]> source.dussan.org Git - jackcess.git/commitdiff
move cursor impls to CursorImpl and IndexCursorImpl
authorJames Ahlborn <jtahlborn@yahoo.com>
Tue, 5 Mar 2013 13:34:47 +0000 (13:34 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Tue, 5 Mar 2013 13:34:47 +0000 (13:34 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jackcess-2@674 f203690c-595d-4dc9-a70b-905162fa7fd2

14 files changed:
TODO.txt
src/java/com/healthmarketscience/jackcess/Cursor.java
src/java/com/healthmarketscience/jackcess/CursorBuilder.java
src/java/com/healthmarketscience/jackcess/CursorImpl.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/DatabaseImpl.java
src/java/com/healthmarketscience/jackcess/ExportUtil.java
src/java/com/healthmarketscience/jackcess/FKEnforcer.java
src/java/com/healthmarketscience/jackcess/IndexCursor.java
src/java/com/healthmarketscience/jackcess/IndexCursorImpl.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/IndexData.java
src/java/com/healthmarketscience/jackcess/Joiner.java
src/java/com/healthmarketscience/jackcess/TableImpl.java
src/java/com/healthmarketscience/jackcess/UsageMap.java
src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java

index ba829a470c02fd005b0b9eec354d3c4d414034c5..e630bdedad08ebd2cfd2b001e55c6760e829fb36 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
@@ -34,3 +34,4 @@ Refactor goals:
 - separate classes into more packages (api,builder,util,impl)
 - remove debug log blocks
 - add Row interface
+- change savepoint to use table number instead of name?
index fa586fdea6d1abb451ee4c1b8ac76ea13e0390e3..e19d4c265827f34c9b1023b2e3b47fecb18b6ac9 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2007 Health Market Science, Inc.
+Copyright (c) 2013 James Ahlborn
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -15,30 +15,14 @@ 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.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.NoSuchElementException;
-
-import com.healthmarketscience.jackcess.TableImpl.RowState;
-import org.apache.commons.lang.ObjectUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
 
 /**
  * Manages iteration for a Table.  Different cursors provide different methods
@@ -56,286 +40,37 @@ import org.apache.commons.logging.LogFactory;
  *
  * @author James Ahlborn
  */
-public abstract class Cursor implements Iterable<Map<String, Object>>
-{  
-  private static final Log LOG = LogFactory.getLog(Cursor.class);  
-
-  /** boolean value indicating forward movement */
-  public static final boolean MOVE_FORWARD = true;
-  /** boolean value indicating reverse movement */
-  public static final boolean MOVE_REVERSE = false;
-  
-  /** first position for the TableScanCursor */
-  private static final ScanPosition FIRST_SCAN_POSITION =
-    new ScanPosition(RowId.FIRST_ROW_ID);
-  /** last position for the TableScanCursor */
-  private static final ScanPosition LAST_SCAN_POSITION =
-    new ScanPosition(RowId.LAST_ROW_ID);
-
-  /** identifier for this cursor */
-  private final Id _id;
-  /** owning table */
-  private final TableImpl _table;
-  /** State used for reading the table rows */
-  private final RowState _rowState;
-  /** the first (exclusive) row id for this cursor */
-  private final Position _firstPos;
-  /** the last (exclusive) row id for this cursor */
-  private final Position _lastPos;
-  /** the previous row */
-  protected Position _prevPos;
-  /** the current row */
-  protected Position _curPos;
-  /** ColumnMatcher to be used when matching column values */
-  protected ColumnMatcher _columnMatcher = SimpleColumnMatcher.INSTANCE;
-
-  protected Cursor(Id id, TableImpl table, Position firstPos, Position lastPos) {
-    _id = id;
-    _table = table;
-    _rowState = _table.createRowState();
-    _firstPos = firstPos;
-    _lastPos = lastPos;
-    _curPos = firstPos;
-    _prevPos = firstPos;
-  }
-
-  /**
-   * Creates a normal, un-indexed cursor for the given table.
-   * @param table the table over which this cursor will traverse
-   */
-  public static Cursor createCursor(TableImpl table) {
-    return new TableScanCursor(table);
-  }
-
-  /**
-   * Creates an indexed cursor for the given table.
-   * <p>
-   * Note, index based table traversal may not include all rows, as certain
-   * types of indexes do not include all entries (namely, some indexes ignore
-   * null entries, see {@link Index#shouldIgnoreNulls}).
-   * 
-   * @param table the table over which this cursor will traverse
-   * @param index index for the table which will define traversal order as
-   *              well as enhance certain lookups
-   */
-  public static Cursor createIndexCursor(TableImpl table, IndexImpl index)
-    throws IOException
-  {
-    return IndexCursor.createCursor(table, index);
-  }
-  
-  /**
-   * Creates an indexed cursor for the given table, narrowed to the given
-   * range.
-   * <p>
-   * Note, index based table traversal may not include all rows, as certain
-   * types of indexes do not include all entries (namely, some indexes ignore
-   * null entries, see {@link Index#shouldIgnoreNulls}).
-   * 
-   * @param table the table over which this cursor will traverse
-   * @param index index for the table which will define traversal order as
-   *              well as enhance certain lookups
-   * @param startRow the first row of data for the cursor (inclusive), or
-   *                 {@code null} for the first entry
-   * @param endRow the last row of data for the cursor (inclusive), or
-   *               {@code null} for the last entry
-   */
-  public static Cursor createIndexCursor(TableImpl table, IndexImpl index,
-                                         Object[] startRow, Object[] endRow)
-    throws IOException
-  {
-    return IndexCursor.createCursor(table, index, startRow, endRow);
-  }
-  
-  /**
-   * Creates an indexed cursor for the given table, narrowed to the given
-   * range.
-   * <p>
-   * Note, index based table traversal may not include all rows, as certain
-   * types of indexes do not include all entries (namely, some indexes ignore
-   * null entries, see {@link Index#shouldIgnoreNulls}).
-   * 
-   * @param table the table over which this cursor will traverse
-   * @param index index for the table which will define traversal order as
-   *              well as enhance certain lookups
-   * @param startRow the first row of data for the cursor, or {@code null} for
-   *                 the first entry
-   * @param startInclusive whether or not startRow is inclusive or exclusive
-   * @param endRow the last row of data for the cursor, or {@code null} for
-   *               the last entry
-   * @param endInclusive whether or not endRow is inclusive or exclusive
-   */
-  public static Cursor createIndexCursor(TableImpl table, IndexImpl index,
-                                         Object[] startRow,
-                                         boolean startInclusive,
-                                         Object[] endRow,
-                                         boolean endInclusive)
-    throws IOException
-  {
-    return IndexCursor.createCursor(table, index, startRow, startInclusive,
-                                    endRow, endInclusive);
-  }
-
-  /**
-   * Convenience method for finding a specific row in a table which matches a
-   * given row "pattern".  See {@link #findFirstRow(Map)} for details on the
-   * rowPattern.
-   * <p>
-   * Warning, this method <i>always</i> starts searching from the beginning of
-   * the Table (you cannot use it to find successive matches).
-   * 
-   * @param table the table to search
-   * @param rowPattern pattern to be used to find the row
-   * @return the matching row or {@code null} if a match could not be found.
-   */
-  public static Map<String,Object> findRow(TableImpl table,
-                                           Map<String,?> rowPattern)
-    throws IOException
-  {
-    Cursor cursor = createCursor(table);
-    if(cursor.findFirstRow(rowPattern)) {
-      return cursor.getCurrentRow();
-    }
-    return null;
-  }
-  
-  /**
-   * Convenience method for finding a specific row in a table which matches a
-   * given row "pattern".  See {@link #findFirstRow(Column,Object)} for
-   * details on the pattern.
-   * <p>
-   * Note, a {@code null} result value is ambiguous in that it could imply no
-   * match or a matching row with {@code null} for the desired value.  If
-   * distinguishing this situation is important, you will need to use a Cursor
-   * directly instead of this convenience method.
-   * 
-   * @param table the table to search
-   * @param column column whose value should be returned
-   * @param columnPattern column being matched by the valuePattern
-   * @param valuePattern value from the columnPattern which will match the
-   *                     desired row
-   * @return the matching row or {@code null} if a match could not be found.
-   */
-  public static Object findValue(TableImpl table, ColumnImpl column,
-                                 ColumnImpl columnPattern, Object valuePattern)
-    throws IOException
-  {
-    Cursor cursor = createCursor(table);
-    if(cursor.findFirstRow(columnPattern, valuePattern)) {
-      return cursor.getCurrentRowValue(column);
-    }
-    return null;
-  }
-  
-  /**
-   * Convenience method for finding a specific row in an indexed table which
-   * matches a given row "pattern".  See {@link #findFirstRow(Map)} for
-   * details on the rowPattern.
-   * <p>
-   * Warning, this method <i>always</i> starts searching from the beginning of
-   * the Table (you cannot use it to find successive matches).
-   * 
-   * @param table the table to search
-   * @param index index to assist the search
-   * @param rowPattern pattern to be used to find the row
-   * @return the matching row or {@code null} if a match could not be found.
-   */
-  public static Map<String,Object> findRow(TableImpl table, IndexImpl index,
-                                           Map<String,?> rowPattern)
-    throws IOException
-  {
-    Cursor cursor = createIndexCursor(table, index);
-    if(cursor.findFirstRow(rowPattern)) {
-      return cursor.getCurrentRow();
-    }
-    return null;
-  }
-  
-  /**
-   * Convenience method for finding a specific row in a table which matches a
-   * given row "pattern".  See {@link #findFirstRow(Column,Object)} for
-   * details on the pattern.
-   * <p>
-   * Note, a {@code null} result value is ambiguous in that it could imply no
-   * match or a matching row with {@code null} for the desired value.  If
-   * distinguishing this situation is important, you will need to use a Cursor
-   * directly instead of this convenience method.
-   * 
-   * @param table the table to search
-   * @param index index to assist the search
-   * @param column column whose value should be returned
-   * @param columnPattern column being matched by the valuePattern
-   * @param valuePattern value from the columnPattern which will match the
-   *                     desired row
-   * @return the matching row or {@code null} if a match could not be found.
-   */
-  public static Object findValue(TableImpl table, IndexImpl index, ColumnImpl column,
-                                 ColumnImpl columnPattern, Object valuePattern)
-    throws IOException
-  {
-    Cursor cursor = createIndexCursor(table, index);
-    if(cursor.findFirstRow(columnPattern, valuePattern)) {
-      return cursor.getCurrentRowValue(column);
-    }
-    return null;
-  }
-  
-  public Id getId() {
-    return _id;
-  }
+public interface Cursor extends Iterable<Map<String, Object>>
+{
 
-  public TableImpl getTable() {
-    return _table;
-  }
+  // FIXME, include iterable and iterator methods?
 
-  public JetFormat getFormat() {
-    return getTable().getFormat();
-  }
+  public Id getId();
 
-  public PageChannel getPageChannel() {
-    return getTable().getPageChannel();
-  }
+  public Table getTable();
 
   /**
    * Gets the currently configured ErrorHandler (always non-{@code null}).
    * This will be used to handle all errors.
    */
-  public ErrorHandler getErrorHandler() {
-    return _rowState.getErrorHandler();
-  }
+  public ErrorHandler getErrorHandler();
 
   /**
    * Sets a new ErrorHandler.  If {@code null}, resets to using the
    * ErrorHandler configured at the Table level.
    */
-  public void setErrorHandler(ErrorHandler newErrorHandler) {
-    _rowState.setErrorHandler(newErrorHandler);
-  }    
+  public void setErrorHandler(ErrorHandler newErrorHandler);
 
   /**
    * Returns the currently configured ColumnMatcher, always non-{@code null}.
    */
-  public ColumnMatcher getColumnMatcher() {
-    return _columnMatcher;
-  }
+  public ColumnMatcher getColumnMatcher();
 
   /**
    * Sets a new ColumnMatcher.  If {@code null}, resets to using the
    * default matcher, {@link SimpleColumnMatcher#INSTANCE}.
    */
-  public void setColumnMatcher(ColumnMatcher columnMatcher) {
-    if(columnMatcher == null) {
-      columnMatcher = getDefaultColumnMatcher();
-    }
-    _columnMatcher = columnMatcher;
-  }
-
-  /**
-   * Returns the default ColumnMatcher for this Cursor.
-   */
-  protected ColumnMatcher getDefaultColumnMatcher() {
-    return SimpleColumnMatcher.INSTANCE;
-  }
+  public void setColumnMatcher(ColumnMatcher columnMatcher);
 
   /**
    * Returns the current state of the cursor which can be restored at a future
@@ -344,9 +79,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * Savepoints may be used across different cursor instances for the same
    * table, but they must have the same {@link Id}.
    */
-  public Savepoint getSavepoint() {
-    return new Savepoint(_id, _curPos, _prevPos);
-  }
+  public Savepoint getSavepoint();
 
   /**
    * Moves the cursor to a savepoint previously returned from
@@ -355,101 +88,42 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         cursorId equal to this cursor's id
    */
   public void restoreSavepoint(Savepoint savepoint)
-    throws IOException
-  {
-    if(!_id.equals(savepoint.getCursorId())) {
-      throw new IllegalArgumentException(
-          "Savepoint " + savepoint + " is not valid for this cursor with id "
-          + _id);
-    }
-    restorePosition(savepoint.getCurrentPosition(),
-                    savepoint.getPreviousPosition());
-  }
-  
-  /**
-   * Returns the first row id (exclusive) as defined by this cursor.
-   */
-  protected Position getFirstPosition() {
-    return _firstPos;
-  }
-  
-  /**
-   * Returns the last row id (exclusive) as defined by this cursor.
-   */
-  protected Position getLastPosition() {
-    return _lastPos;
-  }
+    throws IOException;
 
   /**
    * Resets this cursor for forward traversal.  Calls {@link #beforeFirst}.
    */
-  public void reset() {
-    beforeFirst();
-  }  
+  public void reset();
 
   /**
    * Resets this cursor for forward traversal (sets cursor to before the first
    * row).
    */
-  public void beforeFirst() {
-    reset(MOVE_FORWARD);
-  }
-  
+  public void beforeFirst();
+
   /**
    * Resets this cursor for reverse traversal (sets cursor to after the last
    * row).
    */
-  public void afterLast() {
-    reset(MOVE_REVERSE);
-  }
+  public void afterLast();
 
   /**
    * Returns {@code true} if the cursor is currently positioned before the
    * first row, {@code false} otherwise.
    */
-  public boolean isBeforeFirst()
-    throws IOException
-  {
-    if(getFirstPosition().equals(_curPos)) {
-      return !recheckPosition(MOVE_REVERSE);
-    }
-    return false;
-  }
-  
+  public boolean isBeforeFirst() throws IOException;
+
   /**
    * Returns {@code true} if the cursor is currently positioned after the
    * last row, {@code false} otherwise.
    */
-  public boolean isAfterLast()
-    throws IOException
-  {
-    if(getLastPosition().equals(_curPos)) {
-      return !recheckPosition(MOVE_FORWARD);
-    }
-    return false;
-  }
+  public boolean isAfterLast() throws IOException;
 
   /**
    * Returns {@code true} if the row at which the cursor is currently
    * positioned is deleted, {@code false} otherwise (including invalid rows).
    */
-  public boolean isCurrentRowDeleted()
-    throws IOException
-  {
-    // we need to ensure that the "deleted" flag has been read for this row
-    // (or re-read if the table has been recently modified)
-    TableImpl.positionAtRowData(_rowState, _curPos.getRowId());
-    return _rowState.isDeleted();
-  }
-  
-  /**
-   * Resets this cursor for traversing the given direction.
-   */
-  protected void reset(boolean moveForward) {
-    _curPos = getDirHandler(moveForward).getBeginningPosition();
-    _prevPos = _curPos;
-    _rowState.reset();
-  }  
+  public boolean isCurrentRowDeleted() throws IOException;
 
   /**
    * Returns an Iterable whose iterator() method calls <code>afterLast</code>
@@ -459,9 +133,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * @throws IllegalStateException if an IOException is thrown by one of the
    *         operations, the actual exception will be contained within
    */
-  public Iterable<Map<String, Object>> reverseIterable() {
-    return reverseIterable(null);
-  }
+  public Iterable<Map<String, Object>> reverseIterable();
   
   /**
    * Returns an Iterable whose iterator() method calls <code>afterLast</code>
@@ -473,15 +145,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         operations, the actual exception will be contained within
    */
   public Iterable<Map<String, Object>> reverseIterable(
-      final Collection<String> columnNames)
-  {
-    return new Iterable<Map<String, Object>>() {
-      public Iterator<Map<String, Object>> iterator() {
-        return new RowIterator(columnNames, MOVE_REVERSE);
-      }
-    };
-  }
-  
+      Collection<String> columnNames);
+
   /**
    * Calls <code>beforeFirst</code> on this cursor and returns a modifiable
    * Iterator which will iterate through all the rows of this table.  Use of
@@ -490,11 +155,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * @throws IllegalStateException if an IOException is thrown by one of the
    *         operations, the actual exception will be contained within
    */
-  public Iterator<Map<String, Object>> iterator()
-  {
-    return iterator(null);
-  }
-  
+  public Iterator<Map<String, Object>> iterator();
+
   /**
    * Returns an Iterable whose iterator() method returns the result of a call
    * to {@link #iterator(Collection)}
@@ -502,15 +164,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         operations, the actual exception will be contained within
    */
   public Iterable<Map<String, Object>> iterable(
-      final Collection<String> columnNames)
-  {
-    return new Iterable<Map<String, Object>>() {
-      public Iterator<Map<String, Object>> iterator() {
-        return Cursor.this.iterator(columnNames);
-      }
-    };
-  }
-  
+      Collection<String> columnNames);
+
   /**
    * Calls <code>beforeFirst</code> on this table and returns a modifiable
    * Iterator which will iterate through all the rows of this table, returning
@@ -519,10 +174,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * @throws IllegalStateException if an IOException is thrown by one of the
    *         operations, the actual exception will be contained within
    */
-  public Iterator<Map<String, Object>> iterator(Collection<String> columnNames)
-  {
-    return new RowIterator(columnNames, MOVE_FORWARD);
-  }
+  public Iterator<Map<String, Object>> iterator(Collection<String> columnNames);
 
   /**
    * Returns an Iterable whose iterator() method returns the result of a call
@@ -531,11 +183,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         operations, the actual exception will be contained within
    */
   public Iterable<Map<String, Object>> columnMatchIterable(
-      ColumnImpl columnPattern, Object valuePattern)
-  {
-    return columnMatchIterable(null, columnPattern, valuePattern);
-  }
-  
+      Column columnPattern, Object valuePattern);
+
   /**
    * Calls <code>beforeFirst</code> on this cursor and returns a modifiable
    * Iterator which will iterate through all the rows of this table which
@@ -546,29 +195,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         operations, the actual exception will be contained within
    */
   public Iterator<Map<String, Object>> columnMatchIterator(
-      ColumnImpl columnPattern, Object valuePattern)
-  {
-    return columnMatchIterator(null, columnPattern, valuePattern);
-  }
-  
-  /**
-   * Returns an Iterable whose iterator() method returns the result of a call
-   * to {@link #columnMatchIterator(Collection,Column,Object)}
-   * @throws IllegalStateException if an IOException is thrown by one of the
-   *         operations, the actual exception will be contained within
-   */
-  public Iterable<Map<String, Object>> columnMatchIterable(
-      final Collection<String> columnNames,
-      final ColumnImpl columnPattern, final Object valuePattern)
-  {
-    return new Iterable<Map<String, Object>>() {
-      public Iterator<Map<String, Object>> iterator() {
-        return Cursor.this.columnMatchIterator(
-            columnNames, columnPattern, valuePattern);
-      }
-    };
-  }
-  
+      Column columnPattern, Object valuePattern);
+
   /**
    * Calls <code>beforeFirst</code> on this table and returns a modifiable
    * Iterator which will iterate through all the rows of this table which
@@ -580,10 +208,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         operations, the actual exception will be contained within
    */
   public Iterator<Map<String, Object>> columnMatchIterator(
-      Collection<String> columnNames, ColumnImpl columnPattern, Object valuePattern)
-  {
-    return new ColumnMatchIterator(columnNames, columnPattern, valuePattern);
-  }
+      Collection<String> columnNames, Column columnPattern, Object valuePattern);
 
   /**
    * Returns an Iterable whose iterator() method returns the result of a call
@@ -592,11 +217,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         operations, the actual exception will be contained within
    */
   public Iterable<Map<String, Object>> rowMatchIterable(
-      Map<String,?> rowPattern)
-  {
-    return rowMatchIterable(null, rowPattern);
-  }
-  
+      Map<String,?> rowPattern);
+
   /**
    * Calls <code>beforeFirst</code> on this cursor and returns a modifiable
    * Iterator which will iterate through all the rows of this table which
@@ -607,11 +229,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         operations, the actual exception will be contained within
    */
   public Iterator<Map<String, Object>> rowMatchIterator(
-      Map<String,?> rowPattern)
-  {
-    return rowMatchIterator(null, rowPattern);
-  }
-  
+      Map<String,?> rowPattern);
+
   /**
    * Returns an Iterable whose iterator() method returns the result of a call
    * to {@link #rowMatchIterator(Collection,Map)}
@@ -619,17 +238,9 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         operations, the actual exception will be contained within
    */
   public Iterable<Map<String, Object>> rowMatchIterable(
-      final Collection<String> columnNames,
-      final Map<String,?> rowPattern)
-  {
-    return new Iterable<Map<String, Object>>() {
-      public Iterator<Map<String, Object>> iterator() {
-        return Cursor.this.rowMatchIterator(
-            columnNames, rowPattern);
-      }
-    };
-  }
-  
+      Collection<String> columnNames,
+      Map<String,?> rowPattern);
+
   /**
    * Calls <code>beforeFirst</code> on this table and returns a modifiable
    * Iterator which will iterate through all the rows of this table which
@@ -641,37 +252,28 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         operations, the actual exception will be contained within
    */
   public Iterator<Map<String, Object>> rowMatchIterator(
-      Collection<String> columnNames, Map<String,?> rowPattern)
-  {
-    return new RowMatchIterator(columnNames, rowPattern);
-  }
+      Collection<String> columnNames, Map<String,?> rowPattern);
 
   /**
    * Delete the current row.
    * @throws IllegalStateException if the current row is not valid (at
    *         beginning or end of table), or already deleted.
    */
-  public void deleteCurrentRow() throws IOException {
-    _table.deleteRow(_rowState, _curPos.getRowId());
-  }
+  public void deleteCurrentRow() throws IOException;
 
   /**
    * Update the current row.
    * @throws IllegalStateException if the current row is not valid (at
    *         beginning or end of table), or deleted.
    */
-  public void updateCurrentRow(Object... row) throws IOException {
-    _table.updateRow(_rowState, _curPos.getRowId(), row);
-  }
+  public void updateCurrentRow(Object... row) throws IOException;
 
   /**
    * Moves to the next row in the table and returns it.
    * @return The next row in this table (Column name -> Column value), or
    *         {@code null} if no next row is found
    */
-  public Map<String, Object> getNextRow() throws IOException {
-    return getNextRow(null);
-  }
+  public Map<String, Object> getNextRow() throws IOException;
 
   /**
    * Moves to the next row in the table and returns it.
@@ -680,19 +282,14 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         {@code null} if no next row is found
    */
   public Map<String, Object> getNextRow(Collection<String> columnNames) 
-    throws IOException
-  {
-    return getAnotherRow(columnNames, MOVE_FORWARD);
-  }
+    throws IOException;
 
   /**
    * Moves to the previous row in the table and returns it.
    * @return The previous row in this table (Column name -> Column value), or
    *         {@code null} if no previous row is found
    */
-  public Map<String, Object> getPreviousRow() throws IOException {
-    return getPreviousRow(null);
-  }
+  public Map<String, Object> getPreviousRow() throws IOException;
 
   /**
    * Moves to the previous row in the table and returns it.
@@ -701,144 +298,21 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    *         {@code null} if no previous row is found
    */
   public Map<String, Object> getPreviousRow(Collection<String> columnNames) 
-    throws IOException
-  {
-    return getAnotherRow(columnNames, MOVE_REVERSE);
-  }
-
-
-  /**
-   * Moves to another row in the table based on the given direction and
-   * returns it.
-   * @param columnNames Only column names in this collection will be returned
-   * @return another row in this table (Column name -> Column value), where
-   *         "next" may be backwards if moveForward is {@code false}, or
-   *         {@code null} if there is not another row in the given direction.
-   */
-  private Map<String, Object> getAnotherRow(Collection<String> columnNames,
-                                            boolean moveForward) 
-    throws IOException
-  {
-    if(moveToAnotherRow(moveForward)) {
-      return getCurrentRow(columnNames);
-    }
-    return null;
-  }  
+    throws IOException;
 
   /**
    * Moves to the next row as defined by this cursor.
    * @return {@code true} if a valid next row was found, {@code false}
    *         otherwise
    */
-  public boolean moveToNextRow()
-    throws IOException
-  {
-    return moveToAnotherRow(MOVE_FORWARD);
-  }
+  public boolean moveToNextRow() throws IOException;
 
   /**
    * Moves to the previous row as defined by this cursor.
    * @return {@code true} if a valid previous row was found, {@code false}
    *         otherwise
    */
-  public boolean moveToPreviousRow()
-    throws IOException
-  {
-    return moveToAnotherRow(MOVE_REVERSE);
-  }
-
-  /**
-   * Moves to another row in the given direction as defined by this cursor.
-   * @return {@code true} if another valid row was found in the given
-   *         direction, {@code false} otherwise
-   */
-  private boolean moveToAnotherRow(boolean moveForward)
-    throws IOException
-  {
-    if(_curPos.equals(getDirHandler(moveForward).getEndPosition())) {
-      // already at end, make sure nothing has changed
-      return recheckPosition(moveForward);
-    }
-
-    return moveToAnotherRowImpl(moveForward);
-  }
-
-  /**
-   * Restores a current position for the cursor (current position becomes
-   * previous position).
-   */
-  protected void restorePosition(Position curPos)
-    throws IOException
-  {
-    restorePosition(curPos, _curPos);
-  }
-    
-  /**
-   * Restores a current and previous position for the cursor if the given
-   * positions are different from the current positions.
-   */
-  protected final void restorePosition(Position curPos, Position prevPos)
-    throws IOException
-  {
-    if(!curPos.equals(_curPos) || !prevPos.equals(_prevPos)) {
-      restorePositionImpl(curPos, prevPos);
-    }
-  }
-
-  /**
-   * Restores a current and previous position for the cursor.
-   */
-  protected void restorePositionImpl(Position curPos, Position prevPos)
-    throws IOException
-  {
-    // make the current position previous, and the new position current
-    _prevPos = _curPos;
-    _curPos = curPos;
-    _rowState.reset();
-  }
-  
-  /**
-   * Rechecks the current position if the underlying data structures have been
-   * modified.
-   * @return {@code true} if the cursor ended up in a new position,
-   *         {@code false} otherwise.
-   */
-  private boolean recheckPosition(boolean moveForward)
-    throws IOException
-  {
-    if(isUpToDate()) {
-      // nothing has changed
-      return false;
-    }
-
-    // move the cursor back to the previous position
-    restorePosition(_prevPos);
-    return moveToAnotherRowImpl(moveForward);
-  }
-
-  /**
-   * Does the grunt work of moving the cursor to another position in the given
-   * direction.
-   */
-  private boolean moveToAnotherRowImpl(boolean moveForward)
-    throws IOException
-  {
-    _rowState.reset();
-    _prevPos = _curPos;
-    _curPos = findAnotherPosition(_rowState, _curPos, moveForward);
-    TableImpl.positionAtRowHeader(_rowState, _curPos.getRowId());
-    return(!_curPos.equals(getDirHandler(moveForward).getEndPosition()));
-  }
-  
-  /**
-   * @deprecated renamed to {@link #findFirstRow(Column,Object)} to be more clear
-   */
-  @Deprecated
-  public boolean findRow(ColumnImpl columnPattern, Object valuePattern)
-    throws IOException
-  {
-    return findFirstRow(columnPattern, valuePattern);
-  }
+  public boolean moveToPreviousRow() throws IOException;
 
   /**
    * Moves to the first row (as defined by the cursor) where the given column
@@ -856,27 +330,9 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * @return {@code true} if a valid row was found with the given value,
    *         {@code false} if no row was found
    */
-  public boolean findFirstRow(ColumnImpl columnPattern, Object valuePattern)
-    throws IOException
-  {
-    Position curPos = _curPos;
-    Position prevPos = _prevPos;
-    boolean found = false;
-    try {
-      beforeFirst();
-      found = findNextRowImpl(columnPattern, valuePattern);
-      return found;
-    } finally {
-      if(!found) {
-        try {
-          restorePosition(curPos, prevPos);
-        } catch(IOException e) {
-          LOG.error("Failed restoring position", e);
-        }
-      }
-    }
-  }
-  
+  public boolean findFirstRow(Column columnPattern, Object valuePattern)
+    throws IOException;
+
   /**
    * Moves to the next row (as defined by the cursor) where the given column
    * has the given value.  This may be more efficient on some cursors than
@@ -890,35 +346,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * @return {@code true} if a valid row was found with the given value,
    *         {@code false} if no row was found
    */
-  public boolean findNextRow(ColumnImpl columnPattern, Object valuePattern)
-    throws IOException
-  {
-    Position curPos = _curPos;
-    Position prevPos = _prevPos;
-    boolean found = false;
-    try {
-      found = findNextRowImpl(columnPattern, valuePattern);
-      return found;
-    } finally {
-      if(!found) {
-        try {
-          restorePosition(curPos, prevPos);
-        } catch(IOException e) {
-          LOG.error("Failed restoring position", e);
-        }
-      }
-    }
-  }
-  
-  /**
-   * @deprecated renamed to {@link #findFirstRow(Map)} to be more clear
-   */
-  @Deprecated
-  public boolean findRow(Map<String,?> rowPattern)
-    throws IOException
-  {
-    return findFirstRow(rowPattern);
-  }
+  public boolean findNextRow(Column columnPattern, Object valuePattern)
+    throws IOException;
 
   /**
    * Moves to the first row (as defined by the cursor) where the given columns
@@ -934,26 +363,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * @return {@code true} if a valid row was found with the given values,
    *         {@code false} if no row was found
    */
-  public boolean findFirstRow(Map<String,?> rowPattern)
-    throws IOException
-  {
-    Position curPos = _curPos;
-    Position prevPos = _prevPos;
-    boolean found = false;
-    try {
-      beforeFirst();
-      found = findNextRowImpl(rowPattern);
-      return found;
-    } finally {
-      if(!found) {
-        try {
-          restorePosition(curPos, prevPos);
-        } catch(IOException e) {
-          LOG.error("Failed restoring position", e);
-        }
-      }
-    }
-  }
+  public boolean findFirstRow(Map<String,?> rowPattern) throws IOException;
 
   /**
    * Moves to the next row (as defined by the cursor) where the given columns
@@ -966,25 +376,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * @return {@code true} if a valid row was found with the given values,
    *         {@code false} if no row was found
    */
-  public boolean findNextRow(Map<String,?> rowPattern)
-    throws IOException
-  {
-    Position curPos = _curPos;
-    Position prevPos = _prevPos;
-    boolean found = false;
-    try {
-      found = findNextRowImpl(rowPattern);
-      return found;
-    } finally {
-      if(!found) {
-        try {
-          restorePosition(curPos, prevPos);
-        } catch(IOException e) {
-          LOG.error("Failed restoring position", e);
-        }
-      }
-    }
-  }
+  public boolean findNextRow(Map<String,?> rowPattern) throws IOException;
 
   /**
    * Returns {@code true} if the current row matches the given pattern.
@@ -993,615 +385,84 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * @param valuePattern value which is tested for equality with the
    *                     corresponding value in the current row
    */
-  public boolean currentRowMatches(ColumnImpl columnPattern, Object valuePattern)
-    throws IOException
-  {
-    return _columnMatcher.matches(getTable(), columnPattern.getName(),
-                                  valuePattern,
-                                  getCurrentRowValue(columnPattern));
-  }
-  
+  public boolean currentRowMatches(Column columnPattern, Object valuePattern)
+    throws IOException;
+
   /**
    * Returns {@code true} if the current row matches the given pattern.
    * @param rowPattern column names and values which must be equal to the
    *                   corresponding values in the current row
    */
-  public boolean currentRowMatches(Map<String,?> rowPattern)
-    throws IOException
-  {
-    Map<String,Object> row = getCurrentRow(rowPattern.keySet());
-
-    if(rowPattern.size() != row.size()) {
-      return false;
-    }
-
-    for(Map.Entry<String,Object> e : row.entrySet()) {
-      String columnName = e.getKey();
-      if(!_columnMatcher.matches(getTable(), columnName,
-                                 rowPattern.get(columnName), e.getValue())) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-  
-  /**
-   * Moves to the next row (as defined by the cursor) where the given column
-   * has the given value.  Caller manages save/restore on failure.
-   * <p>
-   * Default implementation scans the table from beginning to end.
-   *
-   * @param columnPattern column from the table for this cursor which is being
-   *                      matched by the valuePattern
-   * @param valuePattern value which is equal to the corresponding value in
-   *                     the matched row
-   * @return {@code true} if a valid row was found with the given value,
-   *         {@code false} if no row was found
-   */
-  protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
-    throws IOException
-  {
-    while(moveToNextRow()) {
-      if(currentRowMatches(columnPattern, valuePattern)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Moves to the next row (as defined by the cursor) where the given columns
-   * have the given values.  Caller manages save/restore on failure.
-   * <p>
-   * Default implementation scans the table from beginning to end.
-   *
-   * @param rowPattern column names and values which must be equal to the
-   *                   corresponding values in the matched row
-   * @return {@code true} if a valid row was found with the given values,
-   *         {@code false} if no row was found
-   */
-  protected boolean findNextRowImpl(Map<String,?> rowPattern)
-    throws IOException
-  {
-    while(moveToNextRow()) {
-      if(currentRowMatches(rowPattern)) {
-        return true;
-      }
-    }
-    return false;
-  }  
+  public boolean currentRowMatches(Map<String,?> rowPattern) throws IOException;
 
   /**
    * Moves forward as many rows as possible up to the given number of rows.
    * @return the number of rows moved.
    */
-  public int moveNextRows(int numRows)
-    throws IOException
-  {
-    return moveSomeRows(numRows, MOVE_FORWARD);
-  }
+  public int moveNextRows(int numRows) throws IOException;
 
   /**
    * Moves backward as many rows as possible up to the given number of rows.
    * @return the number of rows moved.
    */
-  public int movePreviousRows(int numRows)
-    throws IOException
-  {
-    return moveSomeRows(numRows, MOVE_REVERSE);
-  }
-
-  /**
-   * Moves as many rows as possible in the given direction up to the given
-   * number of rows.
-   * @return the number of rows moved.
-   */
-  private int moveSomeRows(int numRows, boolean moveForward)
-    throws IOException
-  {
-    int numMovedRows = 0;
-    while((numMovedRows < numRows) && moveToAnotherRow(moveForward)) {
-      ++numMovedRows;
-    }
-    return numMovedRows;
-  }
+  public int movePreviousRows(int numRows) throws IOException;
 
   /**
    * Returns the current row in this cursor (Column name -> Column value).
    */
-  public Map<String, Object> getCurrentRow()
-    throws IOException
-  {
-    return getCurrentRow(null);
-  }
+  public Map<String, Object> getCurrentRow() throws IOException;
 
   /**
    * Returns the current row in this cursor (Column name -> Column value).
    * @param columnNames Only column names in this collection will be returned
    */
   public Map<String, Object> getCurrentRow(Collection<String> columnNames)
-    throws IOException
-  {
-    return _table.getRow(_rowState, _curPos.getRowId(), columnNames);
-  }
+    throws IOException;
 
   /**
    * Returns the given column from the current row.
    */
-  public Object getCurrentRowValue(ColumnImpl column)
-    throws IOException
-  {
-    return _table.getRowValue(_rowState, _curPos.getRowId(), column);
-  }
+  public Object getCurrentRowValue(Column column) throws IOException;
 
   /**
    * Updates a single value in the current row.
    * @throws IllegalStateException if the current row is not valid (at
    *         beginning or end of table), or deleted.
    */
-  public void setCurrentRowValue(ColumnImpl column, Object value)
-    throws IOException
-  {
-    Object[] row = new Object[_table.getColumnCount()];
-    Arrays.fill(row, Column.KEEP_VALUE);
-    column.setRowValue(row, value);
-    _table.updateRow(_rowState, _curPos.getRowId(), row);
-  }
-
-  /**
-   * Returns {@code true} if this cursor is up-to-date with respect to the
-   * relevant table and related table objects, {@code false} otherwise.
-   */
-  protected boolean isUpToDate() {
-    return _rowState.isUpToDate();
-  }
-
-  @Override
-  public String toString() {
-    return getClass().getSimpleName() + " CurPosition " + _curPos +
-      ", PrevPosition " + _prevPos;
-  }
-    
-  /**
-   * Finds the next non-deleted row after the given row (as defined by this
-   * cursor) and returns the id of the row, where "next" may be backwards if
-   * moveForward is {@code false}.  If there are no more rows, the returned
-   * rowId should equal the value returned by {@link #getLastPosition} if
-   * moving forward and {@link #getFirstPosition} if moving backward.
-   */
-  protected abstract Position findAnotherPosition(RowState rowState,
-                                                  Position curPos,
-                                                  boolean moveForward)
+  public void setCurrentRowValue(Column column, Object value)
     throws IOException;
 
-  /**
-   * Returns the DirHandler for the given movement direction.
-   */
-  protected abstract DirHandler getDirHandler(boolean moveForward);
-
-
-  /**
-   * Base implementation of iterator for this cursor, modifiable.
-   */
-  protected abstract class BaseIterator
-    implements Iterator<Map<String, Object>>
-  {
-    protected final Collection<String> _columnNames;
-    protected Boolean _hasNext;
-    protected boolean _validRow;
-    
-    protected BaseIterator(Collection<String> columnNames)
-    {
-      _columnNames = columnNames;
-    }
-
-    public boolean hasNext() {
-      if(_hasNext == null) {
-        try {
-          _hasNext = findNext();
-          _validRow = _hasNext;
-        } catch(IOException e) {
-          throw new IllegalStateException(e);
-        }
-      }
-      return _hasNext; 
-    }
-    
-    public Map<String, Object> next() {
-      if(!hasNext()) {
-        throw new NoSuchElementException();
-      }
-      try {
-        Map<String, Object> rtn = getCurrentRow(_columnNames);
-        _hasNext = null;
-        return rtn;
-      } catch(IOException e) {
-        throw new IllegalStateException(e);
-      }
-    }
-
-    public void remove() {
-      if(_validRow) {
-        try {
-          deleteCurrentRow();
-          _validRow = false;
-        } catch(IOException e) {
-          throw new IllegalStateException(e);
-        }
-      } else {
-        throw new IllegalStateException("Not at valid row");
-      }
-    }
-
-    protected abstract boolean findNext() throws IOException;
-  }
-
-  
-  /**
-   * Row iterator for this cursor, modifiable.
-   */
-  private final class RowIterator extends BaseIterator
-  {
-    private final boolean _moveForward;
-    
-    private RowIterator(Collection<String> columnNames, boolean moveForward)
-    {
-      super(columnNames);
-      _moveForward = moveForward;
-      reset(_moveForward);
-    }
-
-    @Override
-    protected boolean findNext() throws IOException {
-      return moveToAnotherRow(_moveForward);
-    }
-  }
-
-
-  /**
-   * Row iterator for this cursor, modifiable.
-   */
-  private final class ColumnMatchIterator extends BaseIterator
-  {
-    private final ColumnImpl _columnPattern;
-    private final Object _valuePattern;
-    
-    private ColumnMatchIterator(Collection<String> columnNames,
-                                ColumnImpl columnPattern, Object valuePattern)
-    {
-      super(columnNames);
-      _columnPattern = columnPattern;
-      _valuePattern = valuePattern;
-      beforeFirst();
-    }
-
-    @Override
-    protected boolean findNext() throws IOException {
-      return findNextRow(_columnPattern, _valuePattern);
-    }
-  }
-
-
-  /**
-   * Row iterator for this cursor, modifiable.
-   */
-  private final class RowMatchIterator extends BaseIterator
-  {
-    private final Map<String,?> _rowPattern;
-    
-    private RowMatchIterator(Collection<String> columnNames,
-                             Map<String,?> rowPattern)
-    {
-      super(columnNames);
-      _rowPattern = rowPattern;
-      beforeFirst();
-    }
-
-    @Override
-    protected boolean findNext() throws IOException {
-      return findNextRow(_rowPattern);
-    }
-  }
-
-
-  /**
-   * Handles moving the cursor in a given direction.  Separates cursor
-   * logic from value storage.
-   */
-  protected abstract class DirHandler
-  {
-    public abstract Position getBeginningPosition();
-    public abstract Position getEndPosition();
-  }
-
-  
-  /**
-   * Simple un-indexed cursor.
-   */
-  private static final class TableScanCursor extends Cursor
-  {
-    /** ScanDirHandler for forward traversal */
-    private final ScanDirHandler _forwardDirHandler =
-      new ForwardScanDirHandler();
-    /** ScanDirHandler for backward traversal */
-    private final ScanDirHandler _reverseDirHandler =
-      new ReverseScanDirHandler();
-    /** Cursor over the pages that this table owns */
-    private final UsageMap.PageCursor _ownedPagesCursor;
-    
-    private TableScanCursor(TableImpl table) {
-      super(new Id(table, null), table,
-            FIRST_SCAN_POSITION, LAST_SCAN_POSITION);
-      _ownedPagesCursor = table.getOwnedPagesCursor();
-    }
-
-    @Override
-    protected ScanDirHandler getDirHandler(boolean moveForward) {
-      return (moveForward ? _forwardDirHandler : _reverseDirHandler);
-    }
-
-    @Override
-    protected boolean isUpToDate() {
-      return(super.isUpToDate() && _ownedPagesCursor.isUpToDate());
-    }
-    
-    @Override
-    protected void reset(boolean moveForward) {
-      _ownedPagesCursor.reset(moveForward);
-      super.reset(moveForward);
-    }
-
-    @Override
-    protected void restorePositionImpl(Position curPos, Position prevPos)
-      throws IOException
-    {
-      if(!(curPos instanceof ScanPosition) ||
-         !(prevPos instanceof ScanPosition)) {
-        throw new IllegalArgumentException(
-            "Restored positions must be scan positions");
-      }
-      _ownedPagesCursor.restorePosition(curPos.getRowId().getPageNumber(),
-                                        prevPos.getRowId().getPageNumber());
-      super.restorePositionImpl(curPos, prevPos);
-    }
-
-    @Override
-    protected Position findAnotherPosition(RowState rowState, Position curPos,
-                                           boolean moveForward)
-      throws IOException
-    {
-      ScanDirHandler handler = getDirHandler(moveForward);
-      
-      // figure out how many rows are left on this page so we can find the
-      // next row
-      RowId curRowId = curPos.getRowId();
-      TableImpl.positionAtRowHeader(rowState, curRowId);
-      int currentRowNumber = curRowId.getRowNumber();
-    
-      // loop until we find the next valid row or run out of pages
-      while(true) {
-
-        currentRowNumber = handler.getAnotherRowNumber(currentRowNumber);
-        curRowId = new RowId(curRowId.getPageNumber(), currentRowNumber);
-        TableImpl.positionAtRowHeader(rowState, curRowId);
-        
-        if(!rowState.isValid()) {
-          
-          // load next page
-          curRowId = new RowId(handler.getAnotherPageNumber(),
-                               RowId.INVALID_ROW_NUMBER);
-          TableImpl.positionAtRowHeader(rowState, curRowId);
-          
-          if(!rowState.isHeaderPageNumberValid()) {
-            //No more owned pages.  No more rows.
-            return handler.getEndPosition();
-          }
-
-          // update row count and initial row number
-          currentRowNumber = handler.getInitialRowNumber(
-              rowState.getRowsOnHeaderPage());
-
-        } else if(!rowState.isDeleted()) {
-          
-          // we found a valid, non-deleted row, return it
-          return new ScanPosition(curRowId);
-        }
-        
-      }
-    }
-
-    /**
-     * Handles moving the table scan cursor in a given direction.  Separates
-     * cursor logic from value storage.
-     */
-    private abstract class ScanDirHandler extends DirHandler {
-      public abstract int getAnotherRowNumber(int curRowNumber);
-      public abstract int getAnotherPageNumber();
-      public abstract int getInitialRowNumber(int rowsOnPage);
-    }
-    
-    /**
-     * Handles moving the table scan cursor forward.
-     */
-    private final class ForwardScanDirHandler extends ScanDirHandler {
-      @Override
-      public Position getBeginningPosition() {
-        return getFirstPosition();
-      }
-      @Override
-      public Position getEndPosition() {
-        return getLastPosition();
-      }
-      @Override
-      public int getAnotherRowNumber(int curRowNumber) {
-        return curRowNumber + 1;
-      }
-      @Override
-      public int getAnotherPageNumber() {
-        return _ownedPagesCursor.getNextPage();
-      }
-      @Override
-      public int getInitialRowNumber(int rowsOnPage) {
-        return -1;
-      }
-    }
-    
-    /**
-     * Handles moving the table scan cursor backward.
-     */
-    private final class ReverseScanDirHandler extends ScanDirHandler {
-      @Override
-      public Position getBeginningPosition() {
-        return getLastPosition();
-      }
-      @Override
-      public Position getEndPosition() {
-        return getFirstPosition();
-      }
-      @Override
-      public int getAnotherRowNumber(int curRowNumber) {
-        return curRowNumber - 1;
-      }
-      @Override
-      public int getAnotherPageNumber() {
-        return _ownedPagesCursor.getPreviousPage();
-      }
-      @Override
-      public int getInitialRowNumber(int rowsOnPage) {
-        return rowsOnPage;
-      }
-    }
-    
-  }
-
-
   /**
    * Identifier for a cursor.  Will be equal to any other cursor of the same
    * type for the same table.  Primarily used to check the validity of a
    * Savepoint.
    */
-  public static final class Id
-  {
-    private final String _tableName;
-    private final String _indexName;
-
-    protected Id(TableImpl table, Index index) {
-      _tableName = table.getName();
-      _indexName = ((index != null) ? index.getName() : null);
-    }
-
-    @Override
-    public int hashCode() {
-      return _tableName.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      return((this == o) ||
-             ((o != null) && (getClass() == o.getClass()) &&
-              ObjectUtils.equals(_tableName, ((Id)o)._tableName) &&
-              ObjectUtils.equals(_indexName, ((Id)o)._indexName)));
-    }
-
-    @Override
-    public String toString() {
-      return getClass().getSimpleName() + " " + _tableName + ":" + _indexName;
-    }
+  public interface Id
+  {    
   }
 
-  /**
-   * Value object which represents a complete save state of the cursor.
-   */
-  public static final class Savepoint
-  {
-    private final Id _cursorId;
-    private final Position _curPos;
-    private final Position _prevPos;
-
-    private Savepoint(Id cursorId, Position curPos, Position prevPos) {
-      _cursorId = cursorId;
-      _curPos = curPos;
-      _prevPos = prevPos;
-    }
-
-    public Id getCursorId() {
-      return _cursorId;
-    }
-
-    public Position getCurrentPosition() {
-      return _curPos;
-    }
-
-    private Position getPreviousPosition() {
-      return _prevPos;
-    }
-
-    @Override
-    public String toString() {
-      return getClass().getSimpleName() + " " + _cursorId + " CurPosition " + 
-        _curPos + ", PrevPosition " + _prevPos;
-    }
-  }
-  
   /**
    * Value object which maintains the current position of the cursor.
    */
-  public static abstract class Position
-  {
-    protected Position() {
-    }
-
-    @Override
-    public final int hashCode() {
-      return getRowId().hashCode();
-    }
-    
-    @Override
-    public final boolean equals(Object o) {
-      return((this == o) ||
-             ((o != null) && (getClass() == o.getClass()) && equalsImpl(o)));
-    }
-
+  public interface Position
+  {    
     /**
      * Returns the unique RowId of the position of the cursor.
      */
-    public abstract RowId getRowId();
-
-    /**
-     * Returns {@code true} if the subclass specific info in a Position is
-     * equal, {@code false} otherwise.
-     * @param o object being tested for equality, guaranteed to be the same
-     *          class as this object
-     */
-    protected abstract boolean equalsImpl(Object o);
+    public RowId getRowId();
   }
 
   /**
-   * Value object which maintains the current position of a TableScanCursor.
+   * Value object which represents a complete save state of the cursor.
+   * Savepoints are created by calling {@link Cursor#getSavepoint} and used by
+   * calling {@link Cursor#restoreSavepoint} to return the the cursor state at
+   * the time the Savepoint was created.
    */
-  private static final class ScanPosition extends Position
+  public interface Savepoint
   {
-    private final RowId _rowId;
-
-    private ScanPosition(RowId rowId) {
-      _rowId = rowId;
-    }
-
-    @Override
-    public RowId getRowId() {
-      return _rowId;
-    }
+    public Id getCursorId();
 
-    @Override
-    protected boolean equalsImpl(Object o) {
-      return getRowId().equals(((ScanPosition)o).getRowId());
-    }
-    
-    @Override
-    public String toString() {
-      return "RowId = " + getRowId();
-    }
+    public Position getCurrentPosition();
   }
-  
+
 }
index 3a566590869d3c25834a8dd2ef8449854c692d81..5b845344c2c34382ff47d2d930e8744c8a6c0f35 100644 (file)
@@ -58,7 +58,7 @@ public class CursorBuilder {
   /** whether to start at beginning or end of cursor */
   private boolean _beforeFirst = true;
   /** optional save point to restore to the cursor */
-  private Cursor.Savepoint _savepoint;
+  private CursorImpl.SavepointImpl _savepoint;
   /** ColumnMatcher to be used when matching column values */
   private ColumnMatcher _columnMatcher;
 
@@ -87,7 +87,7 @@ public class CursorBuilder {
   /**
    * Sets a savepoint to restore for the initial position of the cursor.
    */
-  public CursorBuilder restoreSavepoint(Cursor.Savepoint savepoint) {
+  public CursorBuilder restoreSavepoint(CursorImpl.SavepointImpl savepoint) {
     _savepoint = savepoint;
     return this;
   }
@@ -273,14 +273,14 @@ public class CursorBuilder {
    * Returns a new cursor for the table, constructed to the given
    * specifications.
    */
-  public Cursor toCursor()
+  public CursorImpl toCursor()
     throws IOException
   {
-    Cursor cursor = null;
+    CursorImpl cursor = null;
     if(_index == null) {
-      cursor = Cursor.createCursor(_table);
+      cursor = CursorImpl.createCursor(_table);
     } else {
-      cursor = Cursor.createIndexCursor(_table, _index,
+      cursor = CursorImpl.createIndexCursor(_table, _index,
                                         _startRow, _startRowInclusive,
                                         _endRow, _endRowInclusive);
     }
@@ -299,10 +299,10 @@ public class CursorBuilder {
    * Returns a new index cursor for the table, constructed to the given
    * specifications.
    */
-  public IndexCursor toIndexCursor()
+  public IndexCursorImpl toIndexCursor()
     throws IOException
   {
-    return (IndexCursor)toCursor();
+    return (IndexCursorImpl)toCursor();
   }
 
 }
diff --git a/src/java/com/healthmarketscience/jackcess/CursorImpl.java b/src/java/com/healthmarketscience/jackcess/CursorImpl.java
new file mode 100644 (file)
index 0000000..582c8f0
--- /dev/null
@@ -0,0 +1,1363 @@
+/*
+Copyright (c) 2007 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.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import com.healthmarketscience.jackcess.TableImpl.RowState;
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * Manages iteration for a Table.  Different cursors provide different methods
+ * of traversing a table.  Cursors should be fairly robust in the face of
+ * table modification during traversal (although depending on how the table is
+ * traversed, row updates may or may not be seen).  Multiple cursors may
+ * traverse the same table simultaneously.
+ * <p>
+ * The Cursor provides a variety of static utility methods to construct
+ * cursors with given characteristics or easily search for specific values.
+ * For even friendlier and more flexible construction, see
+ * {@link CursorBuilder}.
+ * <p>
+ * Is not thread-safe.
+ *
+ * @author James Ahlborn
+ */
+public abstract class CursorImpl implements Cursor
+{  
+  private static final Log LOG = LogFactory.getLog(CursorImpl.class);  
+
+  /** boolean value indicating forward movement */
+  public static final boolean MOVE_FORWARD = true;
+  /** boolean value indicating reverse movement */
+  public static final boolean MOVE_REVERSE = false;
+  
+  /** first position for the TableScanCursor */
+  private static final ScanPosition FIRST_SCAN_POSITION =
+    new ScanPosition(RowId.FIRST_ROW_ID);
+  /** last position for the TableScanCursor */
+  private static final ScanPosition LAST_SCAN_POSITION =
+    new ScanPosition(RowId.LAST_ROW_ID);
+
+  /** identifier for this cursor */
+  private final IdImpl _id;
+  /** owning table */
+  private final TableImpl _table;
+  /** State used for reading the table rows */
+  private final RowState _rowState;
+  /** the first (exclusive) row id for this cursor */
+  private final PositionImpl _firstPos;
+  /** the last (exclusive) row id for this cursor */
+  private final PositionImpl _lastPos;
+  /** the previous row */
+  protected PositionImpl _prevPos;
+  /** the current row */
+  protected PositionImpl _curPos;
+  /** ColumnMatcher to be used when matching column values */
+  protected ColumnMatcher _columnMatcher = SimpleColumnMatcher.INSTANCE;
+
+  protected CursorImpl(IdImpl id, TableImpl table, PositionImpl firstPos,
+                       PositionImpl lastPos) {
+    _id = id;
+    _table = table;
+    _rowState = _table.createRowState();
+    _firstPos = firstPos;
+    _lastPos = lastPos;
+    _curPos = firstPos;
+    _prevPos = firstPos;
+  }
+
+  /**
+   * Creates a normal, un-indexed cursor for the given table.
+   * @param table the table over which this cursor will traverse
+   */
+  public static CursorImpl createCursor(TableImpl table) {
+    return new TableScanCursor(table);
+  }
+
+  /**
+   * Creates an indexed cursor for the given table.
+   * <p>
+   * Note, index based table traversal may not include all rows, as certain
+   * types of indexes do not include all entries (namely, some indexes ignore
+   * null entries, see {@link Index#shouldIgnoreNulls}).
+   * 
+   * @param table the table over which this cursor will traverse
+   * @param index index for the table which will define traversal order as
+   *              well as enhance certain lookups
+   */
+  public static CursorImpl createIndexCursor(TableImpl table, IndexImpl index)
+    throws IOException
+  {
+    return IndexCursorImpl.createCursor(table, index);
+  }
+  
+  /**
+   * Creates an indexed cursor for the given table, narrowed to the given
+   * range.
+   * <p>
+   * Note, index based table traversal may not include all rows, as certain
+   * types of indexes do not include all entries (namely, some indexes ignore
+   * null entries, see {@link Index#shouldIgnoreNulls}).
+   * 
+   * @param table the table over which this cursor will traverse
+   * @param index index for the table which will define traversal order as
+   *              well as enhance certain lookups
+   * @param startRow the first row of data for the cursor (inclusive), or
+   *                 {@code null} for the first entry
+   * @param endRow the last row of data for the cursor (inclusive), or
+   *               {@code null} for the last entry
+   */
+  public static CursorImpl createIndexCursor(TableImpl table, IndexImpl index,
+                                         Object[] startRow, Object[] endRow)
+    throws IOException
+  {
+    return IndexCursorImpl.createCursor(table, index, startRow, endRow);
+  }
+  
+  /**
+   * Creates an indexed cursor for the given table, narrowed to the given
+   * range.
+   * <p>
+   * Note, index based table traversal may not include all rows, as certain
+   * types of indexes do not include all entries (namely, some indexes ignore
+   * null entries, see {@link Index#shouldIgnoreNulls}).
+   * 
+   * @param table the table over which this cursor will traverse
+   * @param index index for the table which will define traversal order as
+   *              well as enhance certain lookups
+   * @param startRow the first row of data for the cursor, or {@code null} for
+   *                 the first entry
+   * @param startInclusive whether or not startRow is inclusive or exclusive
+   * @param endRow the last row of data for the cursor, or {@code null} for
+   *               the last entry
+   * @param endInclusive whether or not endRow is inclusive or exclusive
+   */
+  public static CursorImpl createIndexCursor(TableImpl table, IndexImpl index,
+                                         Object[] startRow,
+                                         boolean startInclusive,
+                                         Object[] endRow,
+                                         boolean endInclusive)
+    throws IOException
+  {
+    return IndexCursorImpl.createCursor(table, index, startRow, startInclusive,
+                                    endRow, endInclusive);
+  }
+
+  /**
+   * Convenience method for finding a specific row in a table which matches a
+   * given row "pattern".  See {@link #findFirstRow(Map)} for details on the
+   * rowPattern.
+   * <p>
+   * Warning, this method <i>always</i> starts searching from the beginning of
+   * the Table (you cannot use it to find successive matches).
+   * 
+   * @param table the table to search
+   * @param rowPattern pattern to be used to find the row
+   * @return the matching row or {@code null} if a match could not be found.
+   */
+  public static Map<String,Object> findRow(TableImpl table,
+                                           Map<String,?> rowPattern)
+    throws IOException
+  {
+    CursorImpl cursor = createCursor(table);
+    if(cursor.findFirstRow(rowPattern)) {
+      return cursor.getCurrentRow();
+    }
+    return null;
+  }
+  
+  /**
+   * Convenience method for finding a specific row in a table which matches a
+   * given row "pattern".  See {@link #findFirstRow(Column,Object)} for
+   * details on the pattern.
+   * <p>
+   * Note, a {@code null} result value is ambiguous in that it could imply no
+   * match or a matching row with {@code null} for the desired value.  If
+   * distinguishing this situation is important, you will need to use a Cursor
+   * directly instead of this convenience method.
+   * 
+   * @param table the table to search
+   * @param column column whose value should be returned
+   * @param columnPattern column being matched by the valuePattern
+   * @param valuePattern value from the columnPattern which will match the
+   *                     desired row
+   * @return the matching row or {@code null} if a match could not be found.
+   */
+  public static Object findValue(TableImpl table, ColumnImpl column,
+                                 ColumnImpl columnPattern, Object valuePattern)
+    throws IOException
+  {
+    CursorImpl cursor = createCursor(table);
+    if(cursor.findFirstRow(columnPattern, valuePattern)) {
+      return cursor.getCurrentRowValue(column);
+    }
+    return null;
+  }
+  
+  /**
+   * Convenience method for finding a specific row in an indexed table which
+   * matches a given row "pattern".  See {@link #findFirstRow(Map)} for
+   * details on the rowPattern.
+   * <p>
+   * Warning, this method <i>always</i> starts searching from the beginning of
+   * the Table (you cannot use it to find successive matches).
+   * 
+   * @param table the table to search
+   * @param index index to assist the search
+   * @param rowPattern pattern to be used to find the row
+   * @return the matching row or {@code null} if a match could not be found.
+   */
+  public static Map<String,Object> findRow(TableImpl table, IndexImpl index,
+                                           Map<String,?> rowPattern)
+    throws IOException
+  {
+    CursorImpl cursor = createIndexCursor(table, index);
+    if(cursor.findFirstRow(rowPattern)) {
+      return cursor.getCurrentRow();
+    }
+    return null;
+  }
+  
+  /**
+   * Convenience method for finding a specific row in a table which matches a
+   * given row "pattern".  See {@link #findFirstRow(Column,Object)} for
+   * details on the pattern.
+   * <p>
+   * Note, a {@code null} result value is ambiguous in that it could imply no
+   * match or a matching row with {@code null} for the desired value.  If
+   * distinguishing this situation is important, you will need to use a Cursor
+   * directly instead of this convenience method.
+   * 
+   * @param table the table to search
+   * @param index index to assist the search
+   * @param column column whose value should be returned
+   * @param columnPattern column being matched by the valuePattern
+   * @param valuePattern value from the columnPattern which will match the
+   *                     desired row
+   * @return the matching row or {@code null} if a match could not be found.
+   */
+  public static Object findValue(TableImpl table, IndexImpl index, ColumnImpl column,
+                                 ColumnImpl columnPattern, Object valuePattern)
+    throws IOException
+  {
+    CursorImpl cursor = createIndexCursor(table, index);
+    if(cursor.findFirstRow(columnPattern, valuePattern)) {
+      return cursor.getCurrentRowValue(column);
+    }
+    return null;
+  }
+  
+  public IdImpl getId() {
+    return _id;
+  }
+
+  public TableImpl getTable() {
+    return _table;
+  }
+
+  public JetFormat getFormat() {
+    return getTable().getFormat();
+  }
+
+  public PageChannel getPageChannel() {
+    return getTable().getPageChannel();
+  }
+
+  public ErrorHandler getErrorHandler() {
+    return _rowState.getErrorHandler();
+  }
+
+  public void setErrorHandler(ErrorHandler newErrorHandler) {
+    _rowState.setErrorHandler(newErrorHandler);
+  }    
+
+  public ColumnMatcher getColumnMatcher() {
+    return _columnMatcher;
+  }
+
+  public void setColumnMatcher(ColumnMatcher columnMatcher) {
+    if(columnMatcher == null) {
+      columnMatcher = getDefaultColumnMatcher();
+    }
+    _columnMatcher = columnMatcher;
+  }
+
+  /**
+   * Returns the default ColumnMatcher for this Cursor.
+   */
+  protected ColumnMatcher getDefaultColumnMatcher() {
+    return SimpleColumnMatcher.INSTANCE;
+  }
+
+  public SavepointImpl getSavepoint() {
+    return new SavepointImpl(_id, _curPos, _prevPos);
+  }
+
+  public void restoreSavepoint(Savepoint savepoint)
+    throws IOException
+  {
+    restoreSavepoint((SavepointImpl)savepoint);
+  }
+
+  public void restoreSavepoint(SavepointImpl savepoint)
+    throws IOException
+  {
+    if(!_id.equals(savepoint.getCursorId())) {
+      throw new IllegalArgumentException(
+          "Savepoint " + savepoint + " is not valid for this cursor with id "
+          + _id);
+    }
+    restorePosition(savepoint.getCurrentPosition(),
+                    savepoint.getPreviousPosition());
+  }
+  
+  /**
+   * Returns the first row id (exclusive) as defined by this cursor.
+   */
+  protected PositionImpl getFirstPosition() {
+    return _firstPos;
+  }
+  
+  /**
+   * Returns the last row id (exclusive) as defined by this cursor.
+   */
+  protected PositionImpl getLastPosition() {
+    return _lastPos;
+  }
+
+  public void reset() {
+    beforeFirst();
+  }  
+
+  public void beforeFirst() {
+    reset(MOVE_FORWARD);
+  }
+  
+  public void afterLast() {
+    reset(MOVE_REVERSE);
+  }
+
+  public boolean isBeforeFirst() throws IOException
+  {
+    if(getFirstPosition().equals(_curPos)) {
+      return !recheckPosition(MOVE_REVERSE);
+    }
+    return false;
+  }
+  
+  public boolean isAfterLast() throws IOException
+  {
+    if(getLastPosition().equals(_curPos)) {
+      return !recheckPosition(MOVE_FORWARD);
+    }
+    return false;
+  }
+
+  public boolean isCurrentRowDeleted() throws IOException
+  {
+    // we need to ensure that the "deleted" flag has been read for this row
+    // (or re-read if the table has been recently modified)
+    TableImpl.positionAtRowData(_rowState, _curPos.getRowId());
+    return _rowState.isDeleted();
+  }
+  
+  /**
+   * Resets this cursor for traversing the given direction.
+   */
+  protected void reset(boolean moveForward) {
+    _curPos = getDirHandler(moveForward).getBeginningPosition();
+    _prevPos = _curPos;
+    _rowState.reset();
+  }  
+
+  public Iterable<Map<String, Object>> reverseIterable() {
+    return reverseIterable(null);
+  }
+  
+  public Iterable<Map<String, Object>> reverseIterable(
+      final Collection<String> columnNames)
+  {
+    return new Iterable<Map<String, Object>>() {
+      public Iterator<Map<String, Object>> iterator() {
+        return new RowIterator(columnNames, MOVE_REVERSE);
+      }
+    };
+  }
+  
+  public Iterator<Map<String, Object>> iterator()
+  {
+    return iterator(null);
+  }
+  
+  public Iterable<Map<String, Object>> iterable(
+      final Collection<String> columnNames)
+  {
+    return new Iterable<Map<String, Object>>() {
+      public Iterator<Map<String, Object>> iterator() {
+        return CursorImpl.this.iterator(columnNames);
+      }
+    };
+  }
+  
+  public Iterator<Map<String, Object>> iterator(Collection<String> columnNames)
+  {
+    return new RowIterator(columnNames, MOVE_FORWARD);
+  }
+
+  public Iterable<Map<String, Object>> columnMatchIterable(
+      Column columnPattern, Object valuePattern)
+  {
+    return columnMatchIterable((ColumnImpl)columnPattern, valuePattern);
+  }
+
+  public Iterable<Map<String, Object>> columnMatchIterable(
+      ColumnImpl columnPattern, Object valuePattern)
+  {
+    return columnMatchIterable(null, columnPattern, valuePattern);
+  }
+
+  public Iterator<Map<String, Object>> columnMatchIterator(
+      Column columnPattern, Object valuePattern)
+  {
+    return columnMatchIterator((ColumnImpl)columnPattern, valuePattern);
+  } 
+  public Iterator<Map<String, Object>> columnMatchIterator(
+      ColumnImpl columnPattern, Object valuePattern)
+  {
+    return columnMatchIterator(null, columnPattern, valuePattern);
+  }
+
+  public Iterable<Map<String, Object>> columnMatchIterable(
+      Collection<String> columnNames,
+      Column columnPattern, Object valuePattern)
+  {
+    return columnMatchIterable(columnNames, (ColumnImpl)columnPattern, 
+                               valuePattern);
+  } 
+  public Iterable<Map<String, Object>> columnMatchIterable(
+      final Collection<String> columnNames,
+      final ColumnImpl columnPattern, final Object valuePattern)
+  {
+    return new Iterable<Map<String, Object>>() {
+      public Iterator<Map<String, Object>> iterator() {
+        return CursorImpl.this.columnMatchIterator(
+            columnNames, columnPattern, valuePattern);
+      }
+    };
+  }
+
+  public Iterator<Map<String, Object>> columnMatchIterator(
+      Collection<String> columnNames, Column columnPattern, 
+      Object valuePattern)
+  {
+    return columnMatchIterator(columnNames, (ColumnImpl)columnPattern, 
+                               valuePattern);
+  } 
+  public Iterator<Map<String, Object>> columnMatchIterator(
+      Collection<String> columnNames, ColumnImpl columnPattern, 
+      Object valuePattern)
+  {
+    return new ColumnMatchIterator(columnNames, columnPattern, valuePattern);
+  }
+
+  public Iterable<Map<String, Object>> rowMatchIterable(
+      Map<String,?> rowPattern)
+  {
+    return rowMatchIterable(null, rowPattern);
+  }
+  
+  public Iterator<Map<String, Object>> rowMatchIterator(
+      Map<String,?> rowPattern)
+  {
+    return rowMatchIterator(null, rowPattern);
+  }
+  
+  public Iterable<Map<String, Object>> rowMatchIterable(
+      final Collection<String> columnNames,
+      final Map<String,?> rowPattern)
+  {
+    return new Iterable<Map<String, Object>>() {
+      public Iterator<Map<String, Object>> iterator() {
+        return CursorImpl.this.rowMatchIterator(
+            columnNames, rowPattern);
+      }
+    };
+  }
+  
+  public Iterator<Map<String, Object>> rowMatchIterator(
+      Collection<String> columnNames, Map<String,?> rowPattern)
+  {
+    return new RowMatchIterator(columnNames, rowPattern);
+  }
+
+  public void deleteCurrentRow() throws IOException {
+    _table.deleteRow(_rowState, _curPos.getRowId());
+  }
+
+  public void updateCurrentRow(Object... row) throws IOException {
+    _table.updateRow(_rowState, _curPos.getRowId(), row);
+  }
+
+  public Map<String, Object> getNextRow() throws IOException {
+    return getNextRow(null);
+  }
+
+  public Map<String, Object> getNextRow(Collection<String> columnNames) 
+    throws IOException
+  {
+    return getAnotherRow(columnNames, MOVE_FORWARD);
+  }
+
+  public Map<String, Object> getPreviousRow() throws IOException {
+    return getPreviousRow(null);
+  }
+
+  public Map<String, Object> getPreviousRow(Collection<String> columnNames) 
+    throws IOException
+  {
+    return getAnotherRow(columnNames, MOVE_REVERSE);
+  }
+
+
+  /**
+   * Moves to another row in the table based on the given direction and
+   * returns it.
+   * @param columnNames Only column names in this collection will be returned
+   * @return another row in this table (Column name -> Column value), where
+   *         "next" may be backwards if moveForward is {@code false}, or
+   *         {@code null} if there is not another row in the given direction.
+   */
+  private Map<String, Object> getAnotherRow(Collection<String> columnNames,
+                                            boolean moveForward) 
+    throws IOException
+  {
+    if(moveToAnotherRow(moveForward)) {
+      return getCurrentRow(columnNames);
+    }
+    return null;
+  }  
+
+  public boolean moveToNextRow() throws IOException
+  {
+    return moveToAnotherRow(MOVE_FORWARD);
+  }
+
+  public boolean moveToPreviousRow() throws IOException
+  {
+    return moveToAnotherRow(MOVE_REVERSE);
+  }
+
+  /**
+   * Moves to another row in the given direction as defined by this cursor.
+   * @return {@code true} if another valid row was found in the given
+   *         direction, {@code false} otherwise
+   */
+  private boolean moveToAnotherRow(boolean moveForward)
+    throws IOException
+  {
+    if(_curPos.equals(getDirHandler(moveForward).getEndPosition())) {
+      // already at end, make sure nothing has changed
+      return recheckPosition(moveForward);
+    }
+
+    return moveToAnotherRowImpl(moveForward);
+  }
+
+  /**
+   * Restores a current position for the cursor (current position becomes
+   * previous position).
+   */
+  protected void restorePosition(PositionImpl curPos)
+    throws IOException
+  {
+    restorePosition(curPos, _curPos);
+  }
+    
+  /**
+   * Restores a current and previous position for the cursor if the given
+   * positions are different from the current positions.
+   */
+  protected final void restorePosition(PositionImpl curPos, 
+                                       PositionImpl prevPos)
+    throws IOException
+  {
+    if(!curPos.equals(_curPos) || !prevPos.equals(_prevPos)) {
+      restorePositionImpl(curPos, prevPos);
+    }
+  }
+
+  /**
+   * Restores a current and previous position for the cursor.
+   */
+  protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
+    throws IOException
+  {
+    // make the current position previous, and the new position current
+    _prevPos = _curPos;
+    _curPos = curPos;
+    _rowState.reset();
+  }
+  
+  /**
+   * Rechecks the current position if the underlying data structures have been
+   * modified.
+   * @return {@code true} if the cursor ended up in a new position,
+   *         {@code false} otherwise.
+   */
+  private boolean recheckPosition(boolean moveForward)
+    throws IOException
+  {
+    if(isUpToDate()) {
+      // nothing has changed
+      return false;
+    }
+
+    // move the cursor back to the previous position
+    restorePosition(_prevPos);
+    return moveToAnotherRowImpl(moveForward);
+  }
+
+  /**
+   * Does the grunt work of moving the cursor to another position in the given
+   * direction.
+   */
+  private boolean moveToAnotherRowImpl(boolean moveForward)
+    throws IOException
+  {
+    _rowState.reset();
+    _prevPos = _curPos;
+    _curPos = findAnotherPosition(_rowState, _curPos, moveForward);
+    TableImpl.positionAtRowHeader(_rowState, _curPos.getRowId());
+    return(!_curPos.equals(getDirHandler(moveForward).getEndPosition()));
+  }
+
+  public boolean findFirstRow(Column columnPattern, Object valuePattern)
+    throws IOException
+  {
+    return findFirstRow((ColumnImpl)columnPattern, valuePattern);
+  } 
+  public boolean findFirstRow(ColumnImpl columnPattern, Object valuePattern)
+    throws IOException
+  {
+    PositionImpl curPos = _curPos;
+    PositionImpl prevPos = _prevPos;
+    boolean found = false;
+    try {
+      beforeFirst();
+      found = findNextRowImpl(columnPattern, valuePattern);
+      return found;
+    } finally {
+      if(!found) {
+        try {
+          restorePosition(curPos, prevPos);
+        } catch(IOException e) {
+          LOG.error("Failed restoring position", e);
+        }
+      }
+    }
+  }
+
+  public boolean findNextRow(Column columnPattern, Object valuePattern)
+    throws IOException
+  {
+    return findNextRow((ColumnImpl)columnPattern, valuePattern);
+  } 
+  public boolean findNextRow(ColumnImpl columnPattern, Object valuePattern)
+    throws IOException
+  {
+    PositionImpl curPos = _curPos;
+    PositionImpl prevPos = _prevPos;
+    boolean found = false;
+    try {
+      found = findNextRowImpl(columnPattern, valuePattern);
+      return found;
+    } finally {
+      if(!found) {
+        try {
+          restorePosition(curPos, prevPos);
+        } catch(IOException e) {
+          LOG.error("Failed restoring position", e);
+        }
+      }
+    }
+  }
+  
+  public boolean findFirstRow(Map<String,?> rowPattern) throws IOException
+  {
+    PositionImpl curPos = _curPos;
+    PositionImpl prevPos = _prevPos;
+    boolean found = false;
+    try {
+      beforeFirst();
+      found = findNextRowImpl(rowPattern);
+      return found;
+    } finally {
+      if(!found) {
+        try {
+          restorePosition(curPos, prevPos);
+        } catch(IOException e) {
+          LOG.error("Failed restoring position", e);
+        }
+      }
+    }
+  }
+
+  public boolean findNextRow(Map<String,?> rowPattern)
+    throws IOException
+  {
+    PositionImpl curPos = _curPos;
+    PositionImpl prevPos = _prevPos;
+    boolean found = false;
+    try {
+      found = findNextRowImpl(rowPattern);
+      return found;
+    } finally {
+      if(!found) {
+        try {
+          restorePosition(curPos, prevPos);
+        } catch(IOException e) {
+          LOG.error("Failed restoring position", e);
+        }
+      }
+    }
+  }
+
+  public boolean currentRowMatches(Column columnPattern, Object valuePattern)
+    throws IOException
+  {
+    return currentRowMatches((ColumnImpl)columnPattern, valuePattern);
+  }
+
+  public boolean currentRowMatches(ColumnImpl columnPattern, Object valuePattern)
+    throws IOException
+  {
+    return _columnMatcher.matches(getTable(), columnPattern.getName(),
+                                  valuePattern,
+                                  getCurrentRowValue(columnPattern));
+  }
+  
+  public boolean currentRowMatches(Map<String,?> rowPattern)
+    throws IOException
+  {
+    Map<String,Object> row = getCurrentRow(rowPattern.keySet());
+
+    if(rowPattern.size() != row.size()) {
+      return false;
+    }
+
+    for(Map.Entry<String,Object> e : row.entrySet()) {
+      String columnName = e.getKey();
+      if(!_columnMatcher.matches(getTable(), columnName,
+                                 rowPattern.get(columnName), e.getValue())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+  
+  /**
+   * Moves to the next row (as defined by the cursor) where the given column
+   * has the given value.  Caller manages save/restore on failure.
+   * <p>
+   * Default implementation scans the table from beginning to end.
+   *
+   * @param columnPattern column from the table for this cursor which is being
+   *                      matched by the valuePattern
+   * @param valuePattern value which is equal to the corresponding value in
+   *                     the matched row
+   * @return {@code true} if a valid row was found with the given value,
+   *         {@code false} if no row was found
+   */
+  protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
+    throws IOException
+  {
+    while(moveToNextRow()) {
+      if(currentRowMatches(columnPattern, valuePattern)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Moves to the next row (as defined by the cursor) where the given columns
+   * have the given values.  Caller manages save/restore on failure.
+   * <p>
+   * Default implementation scans the table from beginning to end.
+   *
+   * @param rowPattern column names and values which must be equal to the
+   *                   corresponding values in the matched row
+   * @return {@code true} if a valid row was found with the given values,
+   *         {@code false} if no row was found
+   */
+  protected boolean findNextRowImpl(Map<String,?> rowPattern)
+    throws IOException
+  {
+    while(moveToNextRow()) {
+      if(currentRowMatches(rowPattern)) {
+        return true;
+      }
+    }
+    return false;
+  }  
+
+  public int moveNextRows(int numRows) throws IOException
+  {
+    return moveSomeRows(numRows, MOVE_FORWARD);
+  }
+
+  public int movePreviousRows(int numRows) throws IOException
+  {
+    return moveSomeRows(numRows, MOVE_REVERSE);
+  }
+
+  /**
+   * Moves as many rows as possible in the given direction up to the given
+   * number of rows.
+   * @return the number of rows moved.
+   */
+  private int moveSomeRows(int numRows, boolean moveForward)
+    throws IOException
+  {
+    int numMovedRows = 0;
+    while((numMovedRows < numRows) && moveToAnotherRow(moveForward)) {
+      ++numMovedRows;
+    }
+    return numMovedRows;
+  }
+
+  public Map<String, Object> getCurrentRow() throws IOException
+  {
+    return getCurrentRow(null);
+  }
+
+  public Map<String, Object> getCurrentRow(Collection<String> columnNames)
+    throws IOException
+  {
+    return _table.getRow(_rowState, _curPos.getRowId(), columnNames);
+  }
+
+  public Object getCurrentRowValue(Column column)
+    throws IOException
+  {
+    return getCurrentRowValue((ColumnImpl)column);
+  }
+
+  public Object getCurrentRowValue(ColumnImpl column)
+    throws IOException
+  {
+    return _table.getRowValue(_rowState, _curPos.getRowId(), column);
+  }
+
+  public void setCurrentRowValue(Column column, Object value)
+    throws IOException
+  {
+    setCurrentRowValue((ColumnImpl)column, value);
+  }
+
+  public void setCurrentRowValue(ColumnImpl column, Object value)
+    throws IOException
+  {
+    Object[] row = new Object[_table.getColumnCount()];
+    Arrays.fill(row, Column.KEEP_VALUE);
+    column.setRowValue(row, value);
+    _table.updateRow(_rowState, _curPos.getRowId(), row);
+  }
+
+  /**
+   * Returns {@code true} if this cursor is up-to-date with respect to the
+   * relevant table and related table objects, {@code false} otherwise.
+   */
+  protected boolean isUpToDate() {
+    return _rowState.isUpToDate();
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + " CurPosition " + _curPos +
+      ", PrevPosition " + _prevPos;
+  }
+    
+  /**
+   * Finds the next non-deleted row after the given row (as defined by this
+   * cursor) and returns the id of the row, where "next" may be backwards if
+   * moveForward is {@code false}.  If there are no more rows, the returned
+   * rowId should equal the value returned by {@link #getLastPosition} if
+   * moving forward and {@link #getFirstPosition} if moving backward.
+   */
+  protected abstract PositionImpl findAnotherPosition(RowState rowState,
+                                                      PositionImpl curPos,
+                                                      boolean moveForward)
+    throws IOException;
+
+  /**
+   * Returns the DirHandler for the given movement direction.
+   */
+  protected abstract DirHandler getDirHandler(boolean moveForward);
+
+
+  /**
+   * Base implementation of iterator for this cursor, modifiable.
+   */
+  protected abstract class BaseIterator
+    implements Iterator<Map<String, Object>>
+  {
+    protected final Collection<String> _columnNames;
+    protected Boolean _hasNext;
+    protected boolean _validRow;
+    
+    protected BaseIterator(Collection<String> columnNames)
+    {
+      _columnNames = columnNames;
+    }
+
+    public boolean hasNext() {
+      if(_hasNext == null) {
+        try {
+          _hasNext = findNext();
+          _validRow = _hasNext;
+        } catch(IOException e) {
+          throw new IllegalStateException(e);
+        }
+      }
+      return _hasNext; 
+    }
+    
+    public Map<String, Object> next() {
+      if(!hasNext()) {
+        throw new NoSuchElementException();
+      }
+      try {
+        Map<String, Object> rtn = getCurrentRow(_columnNames);
+        _hasNext = null;
+        return rtn;
+      } catch(IOException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+
+    public void remove() {
+      if(_validRow) {
+        try {
+          deleteCurrentRow();
+          _validRow = false;
+        } catch(IOException e) {
+          throw new IllegalStateException(e);
+        }
+      } else {
+        throw new IllegalStateException("Not at valid row");
+      }
+    }
+
+    protected abstract boolean findNext() throws IOException;
+  }
+
+  
+  /**
+   * Row iterator for this cursor, modifiable.
+   */
+  private final class RowIterator extends BaseIterator
+  {
+    private final boolean _moveForward;
+    
+    private RowIterator(Collection<String> columnNames, boolean moveForward)
+    {
+      super(columnNames);
+      _moveForward = moveForward;
+      reset(_moveForward);
+    }
+
+    @Override
+    protected boolean findNext() throws IOException {
+      return moveToAnotherRow(_moveForward);
+    }
+  }
+
+
+  /**
+   * Row iterator for this cursor, modifiable.
+   */
+  private final class ColumnMatchIterator extends BaseIterator
+  {
+    private final ColumnImpl _columnPattern;
+    private final Object _valuePattern;
+    
+    private ColumnMatchIterator(Collection<String> columnNames,
+                                ColumnImpl columnPattern, Object valuePattern)
+    {
+      super(columnNames);
+      _columnPattern = columnPattern;
+      _valuePattern = valuePattern;
+      beforeFirst();
+    }
+
+    @Override
+    protected boolean findNext() throws IOException {
+      return findNextRow(_columnPattern, _valuePattern);
+    }
+  }
+
+
+  /**
+   * Row iterator for this cursor, modifiable.
+   */
+  private final class RowMatchIterator extends BaseIterator
+  {
+    private final Map<String,?> _rowPattern;
+    
+    private RowMatchIterator(Collection<String> columnNames,
+                             Map<String,?> rowPattern)
+    {
+      super(columnNames);
+      _rowPattern = rowPattern;
+      beforeFirst();
+    }
+
+    @Override
+    protected boolean findNext() throws IOException {
+      return findNextRow(_rowPattern);
+    }
+  }
+
+
+  /**
+   * Handles moving the cursor in a given direction.  Separates cursor
+   * logic from value storage.
+   */
+  protected abstract class DirHandler
+  {
+    public abstract PositionImpl getBeginningPosition();
+    public abstract PositionImpl getEndPosition();
+  }
+
+  
+  /**
+   * Simple un-indexed cursor.
+   */
+  private static final class TableScanCursor extends CursorImpl
+  {
+    /** ScanDirHandler for forward traversal */
+    private final ScanDirHandler _forwardDirHandler =
+      new ForwardScanDirHandler();
+    /** ScanDirHandler for backward traversal */
+    private final ScanDirHandler _reverseDirHandler =
+      new ReverseScanDirHandler();
+    /** Cursor over the pages that this table owns */
+    private final UsageMap.PageCursor _ownedPagesCursor;
+    
+    private TableScanCursor(TableImpl table) {
+      super(new IdImpl(table, null), table,
+            FIRST_SCAN_POSITION, LAST_SCAN_POSITION);
+      _ownedPagesCursor = table.getOwnedPagesCursor();
+    }
+
+    @Override
+    protected ScanDirHandler getDirHandler(boolean moveForward) {
+      return (moveForward ? _forwardDirHandler : _reverseDirHandler);
+    }
+
+    @Override
+    protected boolean isUpToDate() {
+      return(super.isUpToDate() && _ownedPagesCursor.isUpToDate());
+    }
+    
+    @Override
+    protected void reset(boolean moveForward) {
+      _ownedPagesCursor.reset(moveForward);
+      super.reset(moveForward);
+    }
+
+    @Override
+    protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
+      throws IOException
+    {
+      if(!(curPos instanceof ScanPosition) ||
+         !(prevPos instanceof ScanPosition)) {
+        throw new IllegalArgumentException(
+            "Restored positions must be scan positions");
+      }
+      _ownedPagesCursor.restorePosition(curPos.getRowId().getPageNumber(),
+                                        prevPos.getRowId().getPageNumber());
+      super.restorePositionImpl(curPos, prevPos);
+    }
+
+    @Override
+    protected PositionImpl findAnotherPosition(
+        RowState rowState, PositionImpl curPos, boolean moveForward)
+      throws IOException
+    {
+      ScanDirHandler handler = getDirHandler(moveForward);
+      
+      // figure out how many rows are left on this page so we can find the
+      // next row
+      RowId curRowId = curPos.getRowId();
+      TableImpl.positionAtRowHeader(rowState, curRowId);
+      int currentRowNumber = curRowId.getRowNumber();
+    
+      // loop until we find the next valid row or run out of pages
+      while(true) {
+
+        currentRowNumber = handler.getAnotherRowNumber(currentRowNumber);
+        curRowId = new RowId(curRowId.getPageNumber(), currentRowNumber);
+        TableImpl.positionAtRowHeader(rowState, curRowId);
+        
+        if(!rowState.isValid()) {
+          
+          // load next page
+          curRowId = new RowId(handler.getAnotherPageNumber(),
+                               RowId.INVALID_ROW_NUMBER);
+          TableImpl.positionAtRowHeader(rowState, curRowId);
+          
+          if(!rowState.isHeaderPageNumberValid()) {
+            //No more owned pages.  No more rows.
+            return handler.getEndPosition();
+          }
+
+          // update row count and initial row number
+          currentRowNumber = handler.getInitialRowNumber(
+              rowState.getRowsOnHeaderPage());
+
+        } else if(!rowState.isDeleted()) {
+          
+          // we found a valid, non-deleted row, return it
+          return new ScanPosition(curRowId);
+        }
+        
+      }
+    }
+
+    /**
+     * Handles moving the table scan cursor in a given direction.  Separates
+     * cursor logic from value storage.
+     */
+    private abstract class ScanDirHandler extends DirHandler {
+      public abstract int getAnotherRowNumber(int curRowNumber);
+      public abstract int getAnotherPageNumber();
+      public abstract int getInitialRowNumber(int rowsOnPage);
+    }
+    
+    /**
+     * Handles moving the table scan cursor forward.
+     */
+    private final class ForwardScanDirHandler extends ScanDirHandler {
+      @Override
+      public PositionImpl getBeginningPosition() {
+        return getFirstPosition();
+      }
+      @Override
+      public PositionImpl getEndPosition() {
+        return getLastPosition();
+      }
+      @Override
+      public int getAnotherRowNumber(int curRowNumber) {
+        return curRowNumber + 1;
+      }
+      @Override
+      public int getAnotherPageNumber() {
+        return _ownedPagesCursor.getNextPage();
+      }
+      @Override
+      public int getInitialRowNumber(int rowsOnPage) {
+        return -1;
+      }
+    }
+    
+    /**
+     * Handles moving the table scan cursor backward.
+     */
+    private final class ReverseScanDirHandler extends ScanDirHandler {
+      @Override
+      public PositionImpl getBeginningPosition() {
+        return getLastPosition();
+      }
+      @Override
+      public PositionImpl getEndPosition() {
+        return getFirstPosition();
+      }
+      @Override
+      public int getAnotherRowNumber(int curRowNumber) {
+        return curRowNumber - 1;
+      }
+      @Override
+      public int getAnotherPageNumber() {
+        return _ownedPagesCursor.getPreviousPage();
+      }
+      @Override
+      public int getInitialRowNumber(int rowsOnPage) {
+        return rowsOnPage;
+      }
+    }
+    
+  }
+
+
+  /**
+   * Identifier for a cursor.  Will be equal to any other cursor of the same
+   * type for the same table.  Primarily used to check the validity of a
+   * Savepoint.
+   */
+  protected static final class IdImpl implements Id
+  {
+    private final String _tableName;
+    private final String _indexName;
+
+    protected IdImpl(TableImpl table, Index index) {
+      _tableName = table.getName();
+      _indexName = ((index != null) ? index.getName() : null);
+    }
+
+    @Override
+    public int hashCode() {
+      return _tableName.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return((this == o) ||
+             ((o != null) && (getClass() == o.getClass()) &&
+              ObjectUtils.equals(_tableName, ((IdImpl)o)._tableName) &&
+              ObjectUtils.equals(_indexName, ((IdImpl)o)._indexName)));
+    }
+
+    @Override
+    public String toString() {
+      return getClass().getSimpleName() + " " + _tableName + ":" + _indexName;
+    }
+  }
+
+  /**
+   * Value object which maintains the current position of the cursor.
+   */
+  protected static abstract class PositionImpl implements Position
+  {
+    protected PositionImpl() {
+    }
+
+    @Override
+    public final int hashCode() {
+      return getRowId().hashCode();
+    }
+    
+    @Override
+    public final boolean equals(Object o) {
+      return((this == o) ||
+             ((o != null) && (getClass() == o.getClass()) && equalsImpl(o)));
+    }
+
+    /**
+     * Returns the unique RowId of the position of the cursor.
+     */
+    public abstract RowId getRowId();
+
+    /**
+     * Returns {@code true} if the subclass specific info in a Position is
+     * equal, {@code false} otherwise.
+     * @param o object being tested for equality, guaranteed to be the same
+     *          class as this object
+     */
+    protected abstract boolean equalsImpl(Object o);
+  }
+
+  /**
+   * Value object which represents a complete save state of the cursor.
+   */
+  protected static final class SavepointImpl implements Savepoint
+  {
+    private final IdImpl _cursorId;
+    private final PositionImpl _curPos;
+    private final PositionImpl _prevPos;
+
+    private SavepointImpl(IdImpl cursorId, PositionImpl curPos, 
+                          PositionImpl prevPos) {
+      _cursorId = cursorId;
+      _curPos = curPos;
+      _prevPos = prevPos;
+    }
+
+    public IdImpl getCursorId() {
+      return _cursorId;
+    }
+
+    public PositionImpl getCurrentPosition() {
+      return _curPos;
+    }
+
+    private PositionImpl getPreviousPosition() {
+      return _prevPos;
+    }
+
+    @Override
+    public String toString() {
+      return getClass().getSimpleName() + " " + _cursorId + " CurPosition " + 
+        _curPos + ", PrevPosition " + _prevPos;
+    }
+  }
+  
+  /**
+   * Value object which maintains the current position of a TableScanCursor.
+   */
+  private static final class ScanPosition extends PositionImpl
+  {
+    private final RowId _rowId;
+
+    private ScanPosition(RowId rowId) {
+      _rowId = rowId;
+    }
+
+    @Override
+    public RowId getRowId() {
+      return _rowId;
+    }
+
+    @Override
+    protected boolean equalsImpl(Object o) {
+      return getRowId().equals(((ScanPosition)o).getRowId());
+    }
+    
+    @Override
+    public String toString() {
+      return "RowId = " + getRowId();
+    }
+  }
+  
+}
index b040eb92b3c11f87e20abf4abdf3bc8d9fac4f3a..30f61db683b982a23b268160081a473a4820b5f2 100644 (file)
@@ -1037,7 +1037,7 @@ public class DatabaseImpl implements Database
       
 
     List<Relationship> relationships = new ArrayList<Relationship>();
-    Cursor cursor = createCursorWithOptionalIndex(
+    CursorImpl cursor = createCursorWithOptionalIndex(
         _relationships, REL_COL_FROM_TABLE, table1.getName());
     collectRelationships(cursor, table1, table2, relationships);
     cursor = createCursorWithOptionalIndex(
@@ -1062,7 +1062,7 @@ public class DatabaseImpl implements Database
     Map<Integer,List<Query.Row>> queryRowMap = 
       new HashMap<Integer,List<Query.Row>>();
     for(Map<String,Object> row :
-          Cursor.createCursor(_systemCatalog).iterable(SYSTEM_CATALOG_COLUMNS))
+          CursorImpl.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))) {
@@ -1073,7 +1073,7 @@ public class DatabaseImpl implements Database
     }
 
     // find all the query rows
-    for(Map<String,Object> row : Cursor.createCursor(_queries)) {
+    for(Map<String,Object> row : CursorImpl.createCursor(_queries)) {
       Query.Row queryRow = new Query.Row(row);
       List<Query.Row> queryRows = queryRowMap.get(queryRow.objectId);
       if(queryRows == null) {
@@ -1216,7 +1216,7 @@ public class DatabaseImpl implements Database
    * given cursor and adds them to the given list.
    */
   private static void collectRelationships(
-      Cursor cursor, TableImpl fromTable, TableImpl toTable,
+      CursorImpl cursor, TableImpl fromTable, TableImpl toTable,
       List<Relationship> relationships)
   {
     for(Map<String,Object> row : cursor) {
@@ -1341,7 +1341,7 @@ public class DatabaseImpl implements Database
   {
     // search for ACEs matching the tableParentId.  use the index on the
     // objectId column if found (should be there)
-    Cursor cursor = createCursorWithOptionalIndex(
+    CursorImpl cursor = createCursorWithOptionalIndex(
         getAccessControlEntries(), ACE_COL_OBJECT_ID, _tableParentId);
     
     for(Map<String, Object> row : cursor) {
@@ -1390,7 +1390,7 @@ public class DatabaseImpl implements Database
    * Creates a Cursor restricted to the given column value if possible (using
    * an existing index), otherwise a simple table cursor.
    */
-  private static Cursor createCursorWithOptionalIndex(
+  private static CursorImpl createCursorWithOptionalIndex(
       TableImpl table, String colName, Object colValue)
     throws IOException
   {
@@ -1403,7 +1403,7 @@ public class DatabaseImpl implements Database
       LOG.info("Could not find expected index on table " + table.getName());
     }
     // use table scan instead
-    return Cursor.createCursor(table);
+    return CursorImpl.createCursor(table);
   }
   
   public void flush() throws IOException {
@@ -1776,7 +1776,7 @@ public class DatabaseImpl implements Database
     public Integer findObjectId(Integer parentId, String name) 
       throws IOException 
     {
-      Cursor cur = findRow(parentId, name);
+      CursorImpl cur = findRow(parentId, name);
       if(cur == null) {  
         return null;
       }
@@ -1788,7 +1788,7 @@ public class DatabaseImpl implements Database
                                            Collection<String> columns) 
       throws IOException 
     {
-      Cursor cur = findRow(parentId, name);
+      CursorImpl cur = findRow(parentId, name);
       return ((cur != null) ? cur.getCurrentRow(columns) : null);
     }
 
@@ -1796,7 +1796,7 @@ public class DatabaseImpl implements Database
         Integer objectId, Collection<String> columns)
       throws IOException
     {
-      Cursor cur = findRow(objectId);
+      CursorImpl cur = findRow(objectId);
       return ((cur != null) ? cur.getCurrentRow(columns) : null);
     }
 
@@ -1819,13 +1819,13 @@ public class DatabaseImpl implements Database
       }
     }
 
-    protected abstract Cursor findRow(Integer parentId, String name)
+    protected abstract CursorImpl findRow(Integer parentId, String name)
       throws IOException;
 
-    protected abstract Cursor findRow(Integer objectId) 
+    protected abstract CursorImpl findRow(Integer objectId) 
       throws IOException;
 
-    protected abstract Cursor getTableNamesCursor() throws IOException;
+    protected abstract CursorImpl getTableNamesCursor() throws IOException;
 
     public abstract TableInfo lookupTable(String tableName)
       throws IOException;
@@ -1848,10 +1848,10 @@ public class DatabaseImpl implements Database
    */
   private final class DefaultTableFinder extends TableFinder
   {
-    private final IndexCursor _systemCatalogCursor;
-    private IndexCursor _systemCatalogIdCursor;
+    private final IndexCursorImpl _systemCatalogCursor;
+    private IndexCursorImpl _systemCatalogIdCursor;
 
-    private DefaultTableFinder(IndexCursor systemCatalogCursor) {
+    private DefaultTableFinder(IndexCursorImpl systemCatalogCursor) {
       _systemCatalogCursor = systemCatalogCursor;
     }
     
@@ -1864,7 +1864,7 @@ public class DatabaseImpl implements Database
     }
 
     @Override
-    protected Cursor findRow(Integer parentId, String name) 
+    protected CursorImpl findRow(Integer parentId, String name) 
       throws IOException 
     {
       return (_systemCatalogCursor.findFirstRowByEntry(parentId, name) ?
@@ -1872,7 +1872,7 @@ public class DatabaseImpl implements Database
     }
 
     @Override
-    protected Cursor findRow(Integer objectId) throws IOException 
+    protected CursorImpl findRow(Integer objectId) throws IOException 
     {
       initIdCursor();
       return (_systemCatalogIdCursor.findFirstRowByEntry(objectId) ?
@@ -1905,7 +1905,7 @@ public class DatabaseImpl implements Database
     }
     
     @Override
-    protected Cursor getTableNamesCursor() throws IOException {
+    protected CursorImpl getTableNamesCursor() throws IOException {
       return new CursorBuilder(_systemCatalog)
         .setIndex(_systemCatalogCursor.getIndex())
         .setStartEntry(_tableParentId, IndexData.MIN_VALUE)
@@ -1934,14 +1934,14 @@ public class DatabaseImpl implements Database
    */
   private final class FallbackTableFinder extends TableFinder
   {
-    private final Cursor _systemCatalogCursor;
+    private final CursorImpl _systemCatalogCursor;
 
-    private FallbackTableFinder(Cursor systemCatalogCursor) {
+    private FallbackTableFinder(CursorImpl systemCatalogCursor) {
       _systemCatalogCursor = systemCatalogCursor;
     }
 
     @Override
-    protected Cursor findRow(Integer parentId, String name) 
+    protected CursorImpl findRow(Integer parentId, String name) 
       throws IOException 
     {
       Map<String,Object> rowPat = new HashMap<String,Object>();
@@ -1952,7 +1952,7 @@ public class DatabaseImpl implements Database
     }
 
     @Override
-    protected Cursor findRow(Integer objectId) throws IOException 
+    protected CursorImpl findRow(Integer objectId) throws IOException 
     {
       ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
       return (_systemCatalogCursor.findFirstRow(idCol, objectId) ?
@@ -1993,7 +1993,7 @@ public class DatabaseImpl implements Database
     }
     
     @Override
-    protected Cursor getTableNamesCursor() throws IOException {
+    protected CursorImpl getTableNamesCursor() throws IOException {
       return _systemCatalogCursor;
     }
 
index 1cb7eb74f4ef6a4b2efd875e6a961533b88b705e..5b37c70d660bf7305a50e5c9bef0c63173086aa8 100644 (file)
@@ -268,7 +268,7 @@ public class ExportUtil {
       char quote, ExportFilter filter)
       throws IOException 
   {
-    exportWriter(Cursor.createCursor(db.getTable(tableName)), out, header,
+    exportWriter(CursorImpl.createCursor(db.getTable(tableName)), out, header,
                  delim, quote, filter);
   }
 
@@ -290,7 +290,7 @@ public class ExportUtil {
    *
    * @see Builder
    */
-  public static void exportWriter(Cursor cursor,
+  public static void exportWriter(CursorImpl cursor,
       BufferedWriter out, boolean header, String delim,
       char quote, ExportFilter filter)
       throws IOException 
@@ -409,7 +409,7 @@ public class ExportUtil {
     private DatabaseImpl _db;
     private String _tableName;
     private String _ext = DEFAULT_FILE_EXT;
-    private Cursor _cursor;
+    private CursorImpl _cursor;
     private String _delim = DEFAULT_DELIMITER;
     private char _quote = DEFAULT_QUOTE_CHAR;
     private ExportFilter _filter = SimpleExportFilter.INSTANCE;
@@ -424,7 +424,7 @@ public class ExportUtil {
       _tableName = tableName;
     }
 
-    public Builder(Cursor cursor) {
+    public Builder(CursorImpl cursor) {
       _cursor = cursor;
     }
 
@@ -438,7 +438,7 @@ public class ExportUtil {
       return this;
     }
 
-    public Builder setCursor(Cursor cursor) {
+    public Builder setCursor(CursorImpl cursor) {
       _cursor = cursor;
       return this;
     }
index b05a0f4cdbbf482526646961891df24e1e3d3e01..e6f1e5fccaca48eb6ce7ecbccb4d3ae8b8f6e144 100644 (file)
@@ -236,7 +236,7 @@ final class FKEnforcer
                                             Object[] newFromRow)
     throws IOException
   {
-    IndexCursor toCursor = joiner.getToCursor();
+    IndexCursorImpl toCursor = joiner.getToCursor();
     List<IndexData.ColumnDescriptor> fromCols = joiner.getColumns();
     List<IndexData.ColumnDescriptor> toCols = joiner.getToIndex().getColumns();
     Object[] toRow = new Object[joiner.getToTable().getColumnCount()];
index acd129fbbef891360a52fd1a95aaf7b4b7d49bd4..b8b4556139ff5f5c251f28352f682f7fa2ad5cb6 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2011 James Ahlborn
+Copyright (c) 2013 James Ahlborn
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -21,151 +21,18 @@ package com.healthmarketscience.jackcess;
 
 import java.io.IOException;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Set;
-
-import com.healthmarketscience.jackcess.TableImpl.RowState;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 
 /**
  * Cursor backed by an index with extended traversal options.
  *
  * @author James Ahlborn
  */
-public class IndexCursor extends Cursor 
+public interface IndexCursor extends Cursor
 {
-  private static final Log LOG = LogFactory.getLog(IndexCursor.class);  
-
-  /** IndexDirHandler for forward traversal */
-  private final IndexDirHandler _forwardDirHandler =
-    new ForwardIndexDirHandler();
-  /** IndexDirHandler for backward traversal */
-  private final IndexDirHandler _reverseDirHandler =
-    new ReverseIndexDirHandler();
-  /** logical index which this cursor is using */
-  private final IndexImpl _index;
-  /** Cursor over the entries of the relevant index */
-  private final IndexData.EntryCursor _entryCursor;
-  /** column names for the index entry columns */
-  private Set<String> _indexEntryPattern;
 
-  private IndexCursor(TableImpl table, IndexImpl index,
-                      IndexData.EntryCursor entryCursor)
-    throws IOException
-  {
-    super(new Id(table, index), table,
-          new IndexPosition(entryCursor.getFirstEntry()),
-          new IndexPosition(entryCursor.getLastEntry()));
-    _index = index;
-    _index.initialize();
-    _entryCursor = entryCursor;
-  }
-
-  /**
-   * Creates an indexed cursor for the given table.
-   * <p>
-   * Note, index based table traversal may not include all rows, as certain
-   * types of indexes do not include all entries (namely, some indexes ignore
-   * null entries, see {@link Index#shouldIgnoreNulls}).
-   * 
-   * @param table the table over which this cursor will traverse
-   * @param index index for the table which will define traversal order as
-   *              well as enhance certain lookups
-   */
-  public static IndexCursor createCursor(TableImpl table, IndexImpl index)
-    throws IOException
-  {
-    return createCursor(table, index, null, null);
-  }
-  
-  /**
-   * Creates an indexed cursor for the given table, narrowed to the given
-   * range.
-   * <p>
-   * Note, index based table traversal may not include all rows, as certain
-   * types of indexes do not include all entries (namely, some indexes ignore
-   * null entries, see {@link Index#shouldIgnoreNulls}).
-   * 
-   * @param table the table over which this cursor will traverse
-   * @param index index for the table which will define traversal order as
-   *              well as enhance certain lookups
-   * @param startRow the first row of data for the cursor (inclusive), or
-   *                 {@code null} for the first entry
-   * @param endRow the last row of data for the cursor (inclusive), or
-   *               {@code null} for the last entry
-   */
-  public static IndexCursor createCursor(
-      TableImpl table, IndexImpl index, Object[] startRow, Object[] endRow)
-    throws IOException
-  {
-    return createCursor(table, index, startRow, true, endRow, true);
-  }
-  
-  /**
-   * Creates an indexed cursor for the given table, narrowed to the given
-   * range.
-   * <p>
-   * Note, index based table traversal may not include all rows, as certain
-   * types of indexes do not include all entries (namely, some indexes ignore
-   * null entries, see {@link Index#shouldIgnoreNulls}).
-   * 
-   * @param table the table over which this cursor will traverse
-   * @param index index for the table which will define traversal order as
-   *              well as enhance certain lookups
-   * @param startRow the first row of data for the cursor, or {@code null} for
-   *                 the first entry
-   * @param startInclusive whether or not startRow is inclusive or exclusive
-   * @param endRow the last row of data for the cursor, or {@code null} for
-   *               the last entry
-   * @param endInclusive whether or not endRow is inclusive or exclusive
-   */
-  public static IndexCursor createCursor(TableImpl table, IndexImpl index,
-                                         Object[] startRow,
-                                         boolean startInclusive,
-                                         Object[] endRow,
-                                         boolean endInclusive)
-    throws IOException
-  {
-    if(table != index.getTable()) {
-      throw new IllegalArgumentException(
-          "Given index is not for given table: " + index + ", " + table);
-    }
-    if(!table.getFormat().INDEXES_SUPPORTED) {
-      throw new IllegalArgumentException(
-          "JetFormat " + table.getFormat() + 
-          " does not currently support index lookups");
-    }
-    if(index.getIndexData().isReadOnly()) {
-      throw new IllegalArgumentException(
-          "Given index " + index + 
-          " is not usable for indexed lookups because it is read-only");
-    }
-    IndexCursor cursor = new IndexCursor(table, index,
-                                         index.cursor(startRow, startInclusive,
-                                                      endRow, endInclusive));
-    // init the column matcher appropriately for the index type
-    cursor.setColumnMatcher(null);
-    return cursor;
-  }  
-
-  public IndexImpl getIndex() {
-    return _index;
-  }
-
-  /**
-   * @deprecated renamed to {@link #findFirstRowByEntry(Object...)} to be more
-   * clear
-   */
-  @Deprecated
-  public boolean findRowByEntry(Object... entryValues) 
-    throws IOException 
-  {
-    return findFirstRowByEntry(entryValues);
-  }
+  public Index getIndex();
 
   /**
    * Moves to the first row (as defined by the cursor) where the index entries
@@ -180,24 +47,7 @@ public class IndexCursor extends Cursor
    *         {@code false} if no row was found
    */
   public boolean findFirstRowByEntry(Object... entryValues) 
-    throws IOException 
-  {
-    Position curPos = _curPos;
-    Position prevPos = _prevPos;
-    boolean found = false;
-    try {
-      found = findFirstRowByEntryImpl(toRowValues(entryValues), true);
-      return found;
-    } finally {
-      if(!found) {
-        try {
-          restorePosition(curPos, prevPos);
-        } catch(IOException e) {
-          LOG.error("Failed restoring position", e);
-        }
-      }
-    }
-  }
+    throws IOException;
 
   /**
    * Moves to the first row (as defined by the cursor) where the index entries
@@ -207,24 +57,7 @@ public class IndexCursor extends Cursor
    * @param entryValues the column values for the index's columns.
    */
   public void findClosestRowByEntry(Object... entryValues) 
-    throws IOException 
-  {
-    Position curPos = _curPos;
-    Position prevPos = _prevPos;
-    boolean found = false;
-    try {
-      findFirstRowByEntryImpl(toRowValues(entryValues), false);
-      found = true;
-    } finally {
-      if(!found) {
-        try {
-          restorePosition(curPos, prevPos);
-        } catch(IOException e) {
-          LOG.error("Failed restoring position", e);
-        }
-      }
-    }
-  }
+    throws IOException;
 
   /**
    * Returns {@code true} if the current row matches the given index entries.
@@ -232,22 +65,16 @@ public class IndexCursor extends Cursor
    * @param entryValues the column values for the index's columns.
    */
   public boolean currentRowMatchesEntry(Object... entryValues) 
-    throws IOException 
-  {
-    return currentRowMatchesEntryImpl(toRowValues(entryValues));
-  }
-  
+    throws IOException;
+
   /**
    * Returns a modifiable Iterator which will iterate through all the rows of
    * this table which match the given index entries.
    * @throws IllegalStateException if an IOException is thrown by one of the
    *         operations, the actual exception will be contained within
    */
-  public Iterator<Map<String,Object>> entryIterator(Object... entryValues)
-  {
-    return entryIterator((Collection<String>)null, entryValues);
-  }
-  
+  public Iterator<Map<String,Object>> entryIterator(Object... entryValues);
+
   /**
    * Returns a modifiable Iterator which will iterate through all the rows of
    * this table which match the given index entries, returning only the given
@@ -256,22 +83,16 @@ public class IndexCursor extends Cursor
    *         operations, the actual exception will be contained within
    */
   public Iterator<Map<String,Object>> entryIterator(
-      Collection<String> columnNames, Object... entryValues)
-  {
-    return new EntryIterator(columnNames, toRowValues(entryValues));
-  }
-  
+      Collection<String> columnNames, Object... entryValues);
+
   /**
    * Returns an Iterable whose iterator() method returns the result of a call
    * to {@link #entryIterator(Object...)}
    * @throws IllegalStateException if an IOException is thrown by one of the
    *         operations, the actual exception will be contained within
    */
-  public Iterable<Map<String,Object>> entryIterable(Object... entryValues)
-  {
-    return entryIterable((Collection<String>)null, entryValues);
-  }
-  
+  public Iterable<Map<String,Object>> entryIterable(Object... entryValues);
+
   /**
    * Returns an Iterable whose iterator() method returns the result of a call
    * to {@link #entryIterator(Collection,Object...)}
@@ -279,327 +100,6 @@ public class IndexCursor extends Cursor
    *         operations, the actual exception will be contained within
    */
   public Iterable<Map<String,Object>> entryIterable(
-      final Collection<String> columnNames, final Object... entryValues)
-  {
-    return new Iterable<Map<String, Object>>() {
-      public Iterator<Map<String, Object>> iterator() {
-        return new EntryIterator(columnNames, toRowValues(entryValues));
-      }
-    };
-  }
-  
-  @Override
-  protected IndexDirHandler getDirHandler(boolean moveForward) {
-    return (moveForward ? _forwardDirHandler : _reverseDirHandler);
-  }
-    
-  @Override
-  protected boolean isUpToDate() {
-    return(super.isUpToDate() && _entryCursor.isUpToDate());
-  }
-    
-  @Override
-  protected void reset(boolean moveForward) {
-    _entryCursor.reset(moveForward);
-    super.reset(moveForward);
-  }
-
-  @Override
-  protected void restorePositionImpl(Position curPos, Position prevPos)
-    throws IOException
-  {
-    if(!(curPos instanceof IndexPosition) ||
-       !(prevPos instanceof IndexPosition)) {
-      throw new IllegalArgumentException(
-          "Restored positions must be index positions");
-    }
-    _entryCursor.restorePosition(((IndexPosition)curPos).getEntry(),
-                                 ((IndexPosition)prevPos).getEntry());
-    super.restorePositionImpl(curPos, prevPos);
-  }
-
-  @Override
-  protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
-    throws IOException
-  {
-    if(!isBeforeFirst()) {
-      // use the default table scan for finding rows mid-cursor
-      return super.findNextRowImpl(columnPattern, valuePattern);
-    }
-
-    // searching for the first match
-    Object[] rowValues = _entryCursor.getIndexData().constructIndexRow(
-        columnPattern.getName(), valuePattern);
-
-    if(rowValues == null) {
-      // bummer, use the default table scan
-      return super.findNextRowImpl(columnPattern, valuePattern);
-    }
-      
-    // sweet, we can use our index
-    if(!findPotentialRow(rowValues, true)) {
-      return false;
-    }
-
-    // either we found a row with the given value, or none exist in the
-    // table
-    return currentRowMatches(columnPattern, valuePattern);
-  }
-
-  /**
-   * Moves to the first row (as defined by the cursor) where the index entries
-   * match the given values.  Caller manages save/restore on failure.
-   *
-   * @param rowValues the column values built from the index column values
-   * @param requireMatch whether or not an exact match is found
-   * @return {@code true} if a valid row was found with the given values,
-   *         {@code false} if no row was found
-   */
-  protected boolean findFirstRowByEntryImpl(Object[] rowValues,
-                                            boolean requireMatch) 
-    throws IOException 
-  {
-    if(!findPotentialRow(rowValues, requireMatch)) {
-      return false;
-    } else if(!requireMatch) {
-      // nothing more to do, we have moved to the closest row
-      return true;
-    }
-
-    return currentRowMatchesEntryImpl(rowValues);
-  }
-
-  @Override
-  protected boolean findNextRowImpl(Map<String,?> rowPattern)
-    throws IOException
-  {
-    if(!isBeforeFirst()) {
-      // use the default table scan for finding rows mid-cursor
-      return super.findNextRowImpl(rowPattern);
-    }
-
-    // searching for the first match
-    IndexData indexData = _entryCursor.getIndexData();
-    Object[] rowValues = indexData.constructIndexRow(rowPattern);
-
-    if(rowValues == null) {
-      // bummer, use the default table scan
-      return super.findNextRowImpl(rowPattern);
-    }
-
-    // sweet, we can use our index
-    if(!findPotentialRow(rowValues, true)) {
-      // at end of index, no potential matches
-      return false;
-    }
-
-    // find actual matching row
-    Map<String,?> indexRowPattern = null;
-    if(rowPattern.size() == indexData.getColumns().size()) {
-      // the rowPattern matches our index columns exactly, so we can
-      // streamline our testing below
-      indexRowPattern = rowPattern;
-    } else {
-      // the rowPattern has more columns than just the index, so we need to
-      // do more work when testing below
-      Map<String,Object> tmpRowPattern = new LinkedHashMap<String,Object>();
-      indexRowPattern = tmpRowPattern;
-      for(IndexData.ColumnDescriptor idxCol : indexData.getColumns()) {
-        tmpRowPattern.put(idxCol.getName(), rowValues[idxCol.getColumnIndex()]);
-      }
-    }
-      
-    // there may be multiple columns which fit the pattern subset used by
-    // the index, so we need to keep checking until our index values no
-    // longer match
-    do {
-
-      if(!currentRowMatches(indexRowPattern)) {
-        // there are no more rows which could possibly match
-        break;
-      }
-
-      // note, if rowPattern == indexRowPattern, no need to do an extra
-      // comparison with the current row
-      if((rowPattern == indexRowPattern) || currentRowMatches(rowPattern)) {
-        // found it!
-        return true;
-      }
-
-    } while(moveToNextRow());
-        
-    // none of the potential rows matched
-    return false;
-  }
-
-  private boolean currentRowMatchesEntryImpl(Object[] rowValues)
-    throws IOException
-  {
-    if(_indexEntryPattern == null) {
-      // init our set of index column names
-      _indexEntryPattern = new HashSet<String>();
-      for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
-        _indexEntryPattern.add(col.getName());
-      }
-    }
-
-    // check the next row to see if it actually matches
-    Map<String,Object> row = getCurrentRow(_indexEntryPattern);
-
-    for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
-      String columnName = col.getName();
-      Object patValue = rowValues[col.getColumnIndex()];
-      Object rowValue = row.get(columnName);
-      if(!_columnMatcher.matches(getTable(), columnName,
-                                 patValue, rowValue)) {
-        return false;
-      }
-    }
-
-    return true;    
-  }
-  
-  private boolean findPotentialRow(Object[] rowValues, boolean requireMatch)
-    throws IOException
-  {
-    _entryCursor.beforeEntry(rowValues);
-    IndexData.Entry startEntry = _entryCursor.getNextEntry();
-    if(requireMatch && !startEntry.getRowId().isValid()) {
-      // at end of index, no potential matches
-      return false;
-    }
-    // move to position and check it out
-    restorePosition(new IndexPosition(startEntry));
-    return true;
-  }
-
-  private Object[] toRowValues(Object[] entryValues)
-  {
-    return _entryCursor.getIndexData().constructIndexRowFromEntry(entryValues);
-  }
-  
-  @Override
-  protected Position findAnotherPosition(RowState rowState, Position curPos,
-                                         boolean moveForward)
-    throws IOException
-  {
-    IndexDirHandler handler = getDirHandler(moveForward);
-    IndexPosition endPos = (IndexPosition)handler.getEndPosition();
-    IndexData.Entry entry = handler.getAnotherEntry();
-    return ((!entry.equals(endPos.getEntry())) ?
-            new IndexPosition(entry) : endPos);
-  }
-
-  @Override
-  protected ColumnMatcher getDefaultColumnMatcher() {
-    if(getIndex().isUnique()) {
-      // text indexes are case-insensitive, therefore we should always use a
-      // case-insensitive matcher for unique indexes.
-      return CaseInsensitiveColumnMatcher.INSTANCE;
-    }
-    return SimpleColumnMatcher.INSTANCE;
-  }
-
-  /**
-   * Handles moving the table index cursor in a given direction.  Separates
-   * cursor logic from value storage.
-   */
-  private abstract class IndexDirHandler extends DirHandler {
-    public abstract IndexData.Entry getAnotherEntry()
-      throws IOException;
-  }
-    
-  /**
-   * Handles moving the table index cursor forward.
-   */
-  private final class ForwardIndexDirHandler extends IndexDirHandler {
-    @Override
-    public Position getBeginningPosition() {
-      return getFirstPosition();
-    }
-    @Override
-    public Position getEndPosition() {
-      return getLastPosition();
-    }
-    @Override
-    public IndexData.Entry getAnotherEntry() throws IOException {
-      return _entryCursor.getNextEntry();
-    }
-  }
-    
-  /**
-   * Handles moving the table index cursor backward.
-   */
-  private final class ReverseIndexDirHandler extends IndexDirHandler {
-    @Override
-    public Position getBeginningPosition() {
-      return getLastPosition();
-    }
-    @Override
-    public Position getEndPosition() {
-      return getFirstPosition();
-    }
-    @Override
-    public IndexData.Entry getAnotherEntry() throws IOException {
-      return _entryCursor.getPreviousEntry();
-    }
-  }    
-    
-  /**
-   * Value object which maintains the current position of an IndexCursor.
-   */
-  private static final class IndexPosition extends Position
-  {
-    private final IndexData.Entry _entry;
-    
-    private IndexPosition(IndexData.Entry entry) {
-      _entry = entry;
-    }
-
-    @Override
-    public RowId getRowId() {
-      return getEntry().getRowId();
-    }
-    
-    public IndexData.Entry getEntry() {
-      return _entry;
-    }
-    
-    @Override
-    protected boolean equalsImpl(Object o) {
-      return getEntry().equals(((IndexPosition)o).getEntry());
-    }
-
-    @Override
-    public String toString() {
-      return "Entry = " + getEntry();
-    }
-  }
-
-  /**
-   * Row iterator (by matching entry) for this cursor, modifiable.
-   */
-  private final class EntryIterator extends BaseIterator
-  {
-    private final Object[] _rowValues;
-    
-    private EntryIterator(Collection<String> columnNames, Object[] rowValues)
-    {
-      super(columnNames);
-      _rowValues = rowValues;
-      try {
-        _hasNext = findFirstRowByEntryImpl(rowValues, true);
-        _validRow = _hasNext;
-      } catch(IOException e) {
-          throw new IllegalStateException(e);
-      }
-    }
-
-    @Override
-    protected boolean findNext() throws IOException {
-      return (moveToNextRow() && currentRowMatchesEntryImpl(_rowValues));
-    }    
-  }
+      Collection<String> columnNames, Object... entryValues);
 
-  
 }
diff --git a/src/java/com/healthmarketscience/jackcess/IndexCursorImpl.java b/src/java/com/healthmarketscience/jackcess/IndexCursorImpl.java
new file mode 100644 (file)
index 0000000..b425d07
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+Copyright (c) 2011 James Ahlborn
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import com.healthmarketscience.jackcess.TableImpl.RowState;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Cursor backed by an index with extended traversal options.
+ *
+ * @author James Ahlborn
+ */
+public class IndexCursorImpl extends CursorImpl implements IndexCursor
+{
+  private static final Log LOG = LogFactory.getLog(IndexCursorImpl.class);  
+
+  /** IndexDirHandler for forward traversal */
+  private final IndexDirHandler _forwardDirHandler =
+    new ForwardIndexDirHandler();
+  /** IndexDirHandler for backward traversal */
+  private final IndexDirHandler _reverseDirHandler =
+    new ReverseIndexDirHandler();
+  /** logical index which this cursor is using */
+  private final IndexImpl _index;
+  /** Cursor over the entries of the relevant index */
+  private final IndexData.EntryCursor _entryCursor;
+  /** column names for the index entry columns */
+  private Set<String> _indexEntryPattern;
+
+  private IndexCursorImpl(TableImpl table, IndexImpl index,
+                          IndexData.EntryCursor entryCursor)
+    throws IOException
+  {
+    super(new IdImpl(table, index), table,
+          new IndexPosition(entryCursor.getFirstEntry()),
+          new IndexPosition(entryCursor.getLastEntry()));
+    _index = index;
+    _index.initialize();
+    _entryCursor = entryCursor;
+  }
+
+  /**
+   * Creates an indexed cursor for the given table.
+   * <p>
+   * Note, index based table traversal may not include all rows, as certain
+   * types of indexes do not include all entries (namely, some indexes ignore
+   * null entries, see {@link Index#shouldIgnoreNulls}).
+   * 
+   * @param table the table over which this cursor will traverse
+   * @param index index for the table which will define traversal order as
+   *              well as enhance certain lookups
+   */
+  public static IndexCursorImpl createCursor(TableImpl table, IndexImpl index)
+    throws IOException
+  {
+    return createCursor(table, index, null, null);
+  }
+  
+  /**
+   * Creates an indexed cursor for the given table, narrowed to the given
+   * range.
+   * <p>
+   * Note, index based table traversal may not include all rows, as certain
+   * types of indexes do not include all entries (namely, some indexes ignore
+   * null entries, see {@link Index#shouldIgnoreNulls}).
+   * 
+   * @param table the table over which this cursor will traverse
+   * @param index index for the table which will define traversal order as
+   *              well as enhance certain lookups
+   * @param startRow the first row of data for the cursor (inclusive), or
+   *                 {@code null} for the first entry
+   * @param endRow the last row of data for the cursor (inclusive), or
+   *               {@code null} for the last entry
+   */
+  public static IndexCursorImpl createCursor(
+      TableImpl table, IndexImpl index, Object[] startRow, Object[] endRow)
+    throws IOException
+  {
+    return createCursor(table, index, startRow, true, endRow, true);
+  }
+  
+  /**
+   * Creates an indexed cursor for the given table, narrowed to the given
+   * range.
+   * <p>
+   * Note, index based table traversal may not include all rows, as certain
+   * types of indexes do not include all entries (namely, some indexes ignore
+   * null entries, see {@link Index#shouldIgnoreNulls}).
+   * 
+   * @param table the table over which this cursor will traverse
+   * @param index index for the table which will define traversal order as
+   *              well as enhance certain lookups
+   * @param startRow the first row of data for the cursor, or {@code null} for
+   *                 the first entry
+   * @param startInclusive whether or not startRow is inclusive or exclusive
+   * @param endRow the last row of data for the cursor, or {@code null} for
+   *               the last entry
+   * @param endInclusive whether or not endRow is inclusive or exclusive
+   */
+  public static IndexCursorImpl createCursor(TableImpl table, IndexImpl index,
+                                         Object[] startRow,
+                                         boolean startInclusive,
+                                         Object[] endRow,
+                                         boolean endInclusive)
+    throws IOException
+  {
+    if(table != index.getTable()) {
+      throw new IllegalArgumentException(
+          "Given index is not for given table: " + index + ", " + table);
+    }
+    if(!table.getFormat().INDEXES_SUPPORTED) {
+      throw new IllegalArgumentException(
+          "JetFormat " + table.getFormat() + 
+          " does not currently support index lookups");
+    }
+    if(index.getIndexData().isReadOnly()) {
+      throw new IllegalArgumentException(
+          "Given index " + index + 
+          " is not usable for indexed lookups because it is read-only");
+    }
+    IndexCursorImpl cursor = new IndexCursorImpl(table, index,
+                                         index.cursor(startRow, startInclusive,
+                                                      endRow, endInclusive));
+    // init the column matcher appropriately for the index type
+    cursor.setColumnMatcher(null);
+    return cursor;
+  }  
+
+  public IndexImpl getIndex() {
+    return _index;
+  }
+
+  public boolean findFirstRowByEntry(Object... entryValues) 
+    throws IOException 
+  {
+    PositionImpl curPos = _curPos;
+    PositionImpl prevPos = _prevPos;
+    boolean found = false;
+    try {
+      found = findFirstRowByEntryImpl(toRowValues(entryValues), true);
+      return found;
+    } finally {
+      if(!found) {
+        try {
+          restorePosition(curPos, prevPos);
+        } catch(IOException e) {
+          LOG.error("Failed restoring position", e);
+        }
+      }
+    }
+  }
+
+  public void findClosestRowByEntry(Object... entryValues) 
+    throws IOException 
+  {
+    PositionImpl curPos = _curPos;
+    PositionImpl prevPos = _prevPos;
+    boolean found = false;
+    try {
+      findFirstRowByEntryImpl(toRowValues(entryValues), false);
+      found = true;
+    } finally {
+      if(!found) {
+        try {
+          restorePosition(curPos, prevPos);
+        } catch(IOException e) {
+          LOG.error("Failed restoring position", e);
+        }
+      }
+    }
+  }
+
+  public boolean currentRowMatchesEntry(Object... entryValues) 
+    throws IOException 
+  {
+    return currentRowMatchesEntryImpl(toRowValues(entryValues));
+  }
+  
+  public Iterator<Map<String,Object>> entryIterator(Object... entryValues)
+  {
+    return entryIterator((Collection<String>)null, entryValues);
+  }
+  
+  public Iterator<Map<String,Object>> entryIterator(
+      Collection<String> columnNames, Object... entryValues)
+  {
+    return new EntryIterator(columnNames, toRowValues(entryValues));
+  }
+  
+  public Iterable<Map<String,Object>> entryIterable(Object... entryValues)
+  {
+    return entryIterable((Collection<String>)null, entryValues);
+  }
+  
+  public Iterable<Map<String,Object>> entryIterable(
+      final Collection<String> columnNames, final Object... entryValues)
+  {
+    return new Iterable<Map<String, Object>>() {
+      public Iterator<Map<String, Object>> iterator() {
+        return new EntryIterator(columnNames, toRowValues(entryValues));
+      }
+    };
+  }
+  
+  @Override
+  protected IndexDirHandler getDirHandler(boolean moveForward) {
+    return (moveForward ? _forwardDirHandler : _reverseDirHandler);
+  }
+    
+  @Override
+  protected boolean isUpToDate() {
+    return(super.isUpToDate() && _entryCursor.isUpToDate());
+  }
+    
+  @Override
+  protected void reset(boolean moveForward) {
+    _entryCursor.reset(moveForward);
+    super.reset(moveForward);
+  }
+
+  @Override
+  protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
+    throws IOException
+  {
+    if(!(curPos instanceof IndexPosition) ||
+       !(prevPos instanceof IndexPosition)) {
+      throw new IllegalArgumentException(
+          "Restored positions must be index positions");
+    }
+    _entryCursor.restorePosition(((IndexPosition)curPos).getEntry(),
+                                 ((IndexPosition)prevPos).getEntry());
+    super.restorePositionImpl(curPos, prevPos);
+  }
+
+  @Override
+  protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
+    throws IOException
+  {
+    if(!isBeforeFirst()) {
+      // use the default table scan for finding rows mid-cursor
+      return super.findNextRowImpl(columnPattern, valuePattern);
+    }
+
+    // searching for the first match
+    Object[] rowValues = _entryCursor.getIndexData().constructIndexRow(
+        columnPattern.getName(), valuePattern);
+
+    if(rowValues == null) {
+      // bummer, use the default table scan
+      return super.findNextRowImpl(columnPattern, valuePattern);
+    }
+      
+    // sweet, we can use our index
+    if(!findPotentialRow(rowValues, true)) {
+      return false;
+    }
+
+    // either we found a row with the given value, or none exist in the
+    // table
+    return currentRowMatches(columnPattern, valuePattern);
+  }
+
+  /**
+   * Moves to the first row (as defined by the cursor) where the index entries
+   * match the given values.  Caller manages save/restore on failure.
+   *
+   * @param rowValues the column values built from the index column values
+   * @param requireMatch whether or not an exact match is found
+   * @return {@code true} if a valid row was found with the given values,
+   *         {@code false} if no row was found
+   */
+  protected boolean findFirstRowByEntryImpl(Object[] rowValues,
+                                            boolean requireMatch) 
+    throws IOException 
+  {
+    if(!findPotentialRow(rowValues, requireMatch)) {
+      return false;
+    } else if(!requireMatch) {
+      // nothing more to do, we have moved to the closest row
+      return true;
+    }
+
+    return currentRowMatchesEntryImpl(rowValues);
+  }
+
+  @Override
+  protected boolean findNextRowImpl(Map<String,?> rowPattern)
+    throws IOException
+  {
+    if(!isBeforeFirst()) {
+      // use the default table scan for finding rows mid-cursor
+      return super.findNextRowImpl(rowPattern);
+    }
+
+    // searching for the first match
+    IndexData indexData = _entryCursor.getIndexData();
+    Object[] rowValues = indexData.constructIndexRow(rowPattern);
+
+    if(rowValues == null) {
+      // bummer, use the default table scan
+      return super.findNextRowImpl(rowPattern);
+    }
+
+    // sweet, we can use our index
+    if(!findPotentialRow(rowValues, true)) {
+      // at end of index, no potential matches
+      return false;
+    }
+
+    // find actual matching row
+    Map<String,?> indexRowPattern = null;
+    if(rowPattern.size() == indexData.getColumns().size()) {
+      // the rowPattern matches our index columns exactly, so we can
+      // streamline our testing below
+      indexRowPattern = rowPattern;
+    } else {
+      // the rowPattern has more columns than just the index, so we need to
+      // do more work when testing below
+      Map<String,Object> tmpRowPattern = new LinkedHashMap<String,Object>();
+      indexRowPattern = tmpRowPattern;
+      for(IndexData.ColumnDescriptor idxCol : indexData.getColumns()) {
+        tmpRowPattern.put(idxCol.getName(), rowValues[idxCol.getColumnIndex()]);
+      }
+    }
+      
+    // there may be multiple columns which fit the pattern subset used by
+    // the index, so we need to keep checking until our index values no
+    // longer match
+    do {
+
+      if(!currentRowMatches(indexRowPattern)) {
+        // there are no more rows which could possibly match
+        break;
+      }
+
+      // note, if rowPattern == indexRowPattern, no need to do an extra
+      // comparison with the current row
+      if((rowPattern == indexRowPattern) || currentRowMatches(rowPattern)) {
+        // found it!
+        return true;
+      }
+
+    } while(moveToNextRow());
+        
+    // none of the potential rows matched
+    return false;
+  }
+
+  private boolean currentRowMatchesEntryImpl(Object[] rowValues)
+    throws IOException
+  {
+    if(_indexEntryPattern == null) {
+      // init our set of index column names
+      _indexEntryPattern = new HashSet<String>();
+      for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
+        _indexEntryPattern.add(col.getName());
+      }
+    }
+
+    // check the next row to see if it actually matches
+    Map<String,Object> row = getCurrentRow(_indexEntryPattern);
+
+    for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
+      String columnName = col.getName();
+      Object patValue = rowValues[col.getColumnIndex()];
+      Object rowValue = row.get(columnName);
+      if(!_columnMatcher.matches(getTable(), columnName,
+                                 patValue, rowValue)) {
+        return false;
+      }
+    }
+
+    return true;    
+  }
+  
+  private boolean findPotentialRow(Object[] rowValues, boolean requireMatch)
+    throws IOException
+  {
+    _entryCursor.beforeEntry(rowValues);
+    IndexData.Entry startEntry = _entryCursor.getNextEntry();
+    if(requireMatch && !startEntry.getRowId().isValid()) {
+      // at end of index, no potential matches
+      return false;
+    }
+    // move to position and check it out
+    restorePosition(new IndexPosition(startEntry));
+    return true;
+  }
+
+  private Object[] toRowValues(Object[] entryValues)
+  {
+    return _entryCursor.getIndexData().constructIndexRowFromEntry(entryValues);
+  }
+  
+  @Override
+  protected PositionImpl findAnotherPosition(RowState rowState, PositionImpl curPos,
+                                         boolean moveForward)
+    throws IOException
+  {
+    IndexDirHandler handler = getDirHandler(moveForward);
+    IndexPosition endPos = (IndexPosition)handler.getEndPosition();
+    IndexData.Entry entry = handler.getAnotherEntry();
+    return ((!entry.equals(endPos.getEntry())) ?
+            new IndexPosition(entry) : endPos);
+  }
+
+  @Override
+  protected ColumnMatcher getDefaultColumnMatcher() {
+    if(getIndex().isUnique()) {
+      // text indexes are case-insensitive, therefore we should always use a
+      // case-insensitive matcher for unique indexes.
+      return CaseInsensitiveColumnMatcher.INSTANCE;
+    }
+    return SimpleColumnMatcher.INSTANCE;
+  }
+
+  /**
+   * Handles moving the table index cursor in a given direction.  Separates
+   * cursor logic from value storage.
+   */
+  private abstract class IndexDirHandler extends DirHandler {
+    public abstract IndexData.Entry getAnotherEntry()
+      throws IOException;
+  }
+    
+  /**
+   * Handles moving the table index cursor forward.
+   */
+  private final class ForwardIndexDirHandler extends IndexDirHandler {
+    @Override
+    public PositionImpl getBeginningPosition() {
+      return getFirstPosition();
+    }
+    @Override
+    public PositionImpl getEndPosition() {
+      return getLastPosition();
+    }
+    @Override
+    public IndexData.Entry getAnotherEntry() throws IOException {
+      return _entryCursor.getNextEntry();
+    }
+  }
+    
+  /**
+   * Handles moving the table index cursor backward.
+   */
+  private final class ReverseIndexDirHandler extends IndexDirHandler {
+    @Override
+    public PositionImpl getBeginningPosition() {
+      return getLastPosition();
+    }
+    @Override
+    public PositionImpl getEndPosition() {
+      return getFirstPosition();
+    }
+    @Override
+    public IndexData.Entry getAnotherEntry() throws IOException {
+      return _entryCursor.getPreviousEntry();
+    }
+  }    
+    
+  /**
+   * Value object which maintains the current position of an IndexCursor.
+   */
+  private static final class IndexPosition extends PositionImpl
+  {
+    private final IndexData.Entry _entry;
+    
+    private IndexPosition(IndexData.Entry entry) {
+      _entry = entry;
+    }
+
+    @Override
+    public RowId getRowId() {
+      return getEntry().getRowId();
+    }
+    
+    public IndexData.Entry getEntry() {
+      return _entry;
+    }
+    
+    @Override
+    protected boolean equalsImpl(Object o) {
+      return getEntry().equals(((IndexPosition)o).getEntry());
+    }
+
+    @Override
+    public String toString() {
+      return "Entry = " + getEntry();
+    }
+  }
+
+  /**
+   * Row iterator (by matching entry) for this cursor, modifiable.
+   */
+  private final class EntryIterator extends BaseIterator
+  {
+    private final Object[] _rowValues;
+    
+    private EntryIterator(Collection<String> columnNames, Object[] rowValues)
+    {
+      super(columnNames);
+      _rowValues = rowValues;
+      try {
+        _hasNext = findFirstRowByEntryImpl(rowValues, true);
+        _validRow = _hasNext;
+      } catch(IOException e) {
+          throw new IllegalStateException(e);
+      }
+    }
+
+    @Override
+    protected boolean findNext() throws IOException {
+      return (moveToNextRow() && currentRowMatchesEntryImpl(_rowValues));
+    }    
+  }
+
+  
+}
index 965e7d80d8ac7e6e295b6f7e4d1e60de4125c240..b2be17dfedb7e2a511e854e4cae5670bd3363034 100644 (file)
@@ -638,7 +638,7 @@ public class IndexData {
       Position tmpPos = null;
       Position endPos = cursor._lastPos;
       while(!endPos.equals(
-                tmpPos = cursor.getAnotherPosition(Cursor.MOVE_FORWARD))) {
+                tmpPos = cursor.getAnotherPosition(CursorImpl.MOVE_FORWARD))) {
         if(tmpPos.getEntry().getRowId().equals(oldEntry.getRowId())) {
           dataPage = tmpPos.getDataPage();
           idx = tmpPos.getIndex();
@@ -1981,11 +1981,11 @@ public class IndexData {
     }
 
     public void beforeFirst() {
-      reset(Cursor.MOVE_FORWARD);
+      reset(CursorImpl.MOVE_FORWARD);
     }
 
     public void afterLast() {
-      reset(Cursor.MOVE_REVERSE);
+      reset(CursorImpl.MOVE_REVERSE);
     }
 
     protected void reset(boolean moveForward)
@@ -2021,7 +2021,7 @@ public class IndexData {
      *         {@code #getLastEntry} otherwise
      */
     public Entry getNextEntry() throws IOException {
-      return getAnotherPosition(Cursor.MOVE_FORWARD).getEntry();
+      return getAnotherPosition(CursorImpl.MOVE_FORWARD).getEntry();
     }
 
     /**
@@ -2029,7 +2029,7 @@ public class IndexData {
      *         {@code #getFirstEntry} otherwise
      */
     public Entry getPreviousEntry() throws IOException {
-      return getAnotherPosition(Cursor.MOVE_REVERSE).getEntry();
+      return getAnotherPosition(CursorImpl.MOVE_REVERSE).getEntry();
     }
 
     /**
index cf25e29384bf327ef234adf56056937f63170719..81820dc83d2d1f5cc569b97888630f8173ed5b73 100644 (file)
@@ -36,10 +36,10 @@ public class Joiner
 {
   private final IndexImpl _fromIndex;
   private final List<IndexData.ColumnDescriptor> _fromCols;
-  private final IndexCursor _toCursor;
+  private final IndexCursorImpl _toCursor;
   private final Object[] _entryValues;
   
-  private Joiner(IndexImpl fromIndex, IndexCursor toCursor)
+  private Joiner(IndexImpl fromIndex, IndexCursorImpl toCursor)
   {
     _fromIndex = fromIndex;
     _fromCols = _fromIndex.getColumns();
@@ -73,7 +73,7 @@ public class Joiner
     throws IOException
   {
     IndexImpl toIndex = fromIndex.getReferencedIndex();
-    IndexCursor toCursor = IndexCursor.createCursor(
+    IndexCursorImpl toCursor = IndexCursorImpl.createCursor(
         toIndex.getTable(), toIndex);
     // text lookups are always case-insensitive
     toCursor.setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE);
@@ -106,7 +106,7 @@ public class Joiner
     return getToCursor().getIndex();
   }
   
-  public IndexCursor getToCursor() {
+  public IndexCursorImpl getToCursor() {
     return _toCursor;
   }
 
index 9df4ce2ec32a45a41f8231d2f8865fefeaa62c5f..a4e21a436a426a2be329f29a5adc1be468c10d51 100644 (file)
@@ -171,7 +171,7 @@ public class TableImpl implements Table
 
   /** common cursor for iterating through the table, kept here for historic
       reasons */
-  private Cursor _cursor;
+  private CursorImpl _cursor;
   
   /**
    * Only used by unit tests
@@ -403,9 +403,9 @@ public class TableImpl implements Table
     return _logicalIndexCount;
   }
 
-  private Cursor getInternalCursor() {
+  private CursorImpl getInternalCursor() {
     if(_cursor == null) {
-      _cursor = Cursor.createCursor(this);
+      _cursor = CursorImpl.createCursor(this);
     }
     return _cursor;
   }
index 292c9bfa83315359e63b4f187cdd46ff8bed04e4..6e3b560d4c564707a967f3dfc7ad4495f0fe673e 100644 (file)
@@ -811,7 +811,7 @@ public class UsageMap
      *         {@link RowId#LAST_PAGE_NUMBER} otherwise
      */
     public int getNextPage() {
-      return getAnotherPage(Cursor.MOVE_FORWARD);
+      return getAnotherPage(CursorImpl.MOVE_FORWARD);
     }
 
     /**
@@ -819,7 +819,7 @@ public class UsageMap
      *         {@link RowId#FIRST_PAGE_NUMBER} otherwise
      */
     public int getPreviousPage() {
-      return getAnotherPage(Cursor.MOVE_REVERSE);
+      return getAnotherPage(CursorImpl.MOVE_REVERSE);
     }
 
     /**
@@ -857,7 +857,7 @@ public class UsageMap
      * page in the map
      */
     public void beforeFirst() {
-      reset(Cursor.MOVE_FORWARD);
+      reset(CursorImpl.MOVE_FORWARD);
     }
 
     /**
@@ -865,7 +865,7 @@ public class UsageMap
      * last page in the map
      */
     public void afterLast() {
-      reset(Cursor.MOVE_REVERSE);
+      reset(CursorImpl.MOVE_REVERSE);
     }
 
     /**
index 73a88940334732fb0fd6f41f960035cec0d1e4f2..af80531a14f2447ed8cee34625177740d69c906b 100644 (file)
@@ -33,7 +33,7 @@ import com.healthmarketscience.jackcess.ColumnImpl;
 import com.healthmarketscience.jackcess.CursorBuilder;
 import com.healthmarketscience.jackcess.DataType;
 import com.healthmarketscience.jackcess.DatabaseImpl;
-import com.healthmarketscience.jackcess.IndexCursor;
+import com.healthmarketscience.jackcess.IndexCursorImpl;
 import com.healthmarketscience.jackcess.JetFormat;
 import com.healthmarketscience.jackcess.PageChannel;
 import com.healthmarketscience.jackcess.TableImpl;
@@ -63,8 +63,8 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
   private final List<ColumnImpl> _typeCols;
   private final ColumnImpl _pkCol;
   private final ColumnImpl _complexValFkCol;
-  private IndexCursor _pkCursor;
-  private IndexCursor _complexValIdCursor;
+  private IndexCursorImpl _pkCursor;
+  private IndexCursorImpl _complexValIdCursor;
   
   protected ComplexColumnInfo(ColumnImpl column, int complexTypeId,
                               TableImpl typeObjTable, TableImpl flatTable)
@@ -111,7 +111,7 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
 
     DatabaseImpl db = column.getDatabase();
     TableImpl complexColumns = db.getSystemComplexColumns();
-    IndexCursor cursor = IndexCursor.createCursor(
+    IndexCursorImpl cursor = IndexCursorImpl.createCursor(
         complexColumns, complexColumns.getPrimaryKeyIndex());
     if(!cursor.findFirstRowByEntry(complexTypeId)) {
       throw new IOException(