]> source.dussan.org Git - jackcess.git/commitdiff
make usagemap cursor work similarly to Cursor; handle live additions and deletions...
authorJames Ahlborn <jtahlborn@yahoo.com>
Mon, 26 Nov 2007 20:18:09 +0000 (20:18 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Mon, 26 Nov 2007 20:18:09 +0000 (20:18 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@183 f203690c-595d-4dc9-a70b-905162fa7fd2

src/java/com/healthmarketscience/jackcess/Cursor.java
src/java/com/healthmarketscience/jackcess/Index.java
src/java/com/healthmarketscience/jackcess/RowId.java
src/java/com/healthmarketscience/jackcess/Table.java
src/java/com/healthmarketscience/jackcess/UsageMap.java
test/src/java/com/healthmarketscience/jackcess/CursorTest.java

index be6e5e72649bec581700a71253c90339c3e4f290..5ef2c24e9c9c7c9f40a6dba789c03296be060aaa 100644 (file)
@@ -34,6 +34,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
   private final RowId _firstRowId;
   /** the last (exclusive) row id for this iterator */
   private final RowId _lastRowId;
+  /** the previous row */
+  private RowId _previousRowId;
   /** the current row */
   private RowId _currentRowId;
   
@@ -44,6 +46,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
     _firstRowId = firstRowId;
     _lastRowId = lastRowId;
     _currentRowId = firstRowId;
+    _previousRowId = firstRowId;
   }
 
   /**
@@ -159,16 +162,26 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    * Returns {@code true} if the cursor is currently positioned before the
    * first row, {@code false} otherwise.
    */
-  public boolean isBeforeFirst() {
-    return getFirstRowId().equals(_currentRowId);
+  public boolean isBeforeFirst()
+    throws IOException
+  {
+    if(getFirstRowId().equals(_currentRowId)) {
+      return !recheckPosition(false);
+    }
+    return false;
   }
   
   /**
    * Returns {@code true} if the cursor is currently positioned after the
    * last row, {@code false} otherwise.
    */
-  public boolean isAfterLast() {
-    return getLastRowId().equals(_currentRowId);
+  public boolean isAfterLast()
+    throws IOException
+  {
+    if(getLastRowId().equals(_currentRowId)) {
+      return !recheckPosition(true);
+    }
+    return false;
   }
 
   /**
@@ -179,6 +192,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
     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)
     Table.positionAtRowData(_rowState, _currentRowId);
     return _rowState.isDeleted();
   }
@@ -188,6 +202,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    */
   protected void reset(boolean moveForward) {
     _currentRowId = getDirHandler(moveForward).getBeginningRowId();
+    _previousRowId = _currentRowId;
     _rowState.reset();
   }  
 
@@ -350,16 +365,58 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
   {
     RowId endRowId = getDirHandler(moveForward).getEndRowId();
     if(_currentRowId.equals(endRowId)) {
-      // already at end
+      // already at end, make sure nothing has changed
+      return recheckPosition(moveForward);
+    }
+
+    return moveToAnotherRowImpl(moveForward);
+  }
+
+  /**
+   * Restores the current position to the previous position.
+   */
+  protected void restorePreviousPosition()
+    throws IOException
+  {
+    // essentially swap current and previous
+    RowId tmp = _previousRowId;
+    _previousRowId = _currentRowId;
+    _currentRowId = tmp;
+  }
+
+  /**
+   * 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
+    restorePreviousPosition();
+    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();
+    _previousRowId = _currentRowId;
     _currentRowId = findAnotherRowId(_rowState, _currentRowId, moveForward);
     Table.positionAtRowHeader(_rowState, _currentRowId);
-    return(!_currentRowId.equals(endRowId));
+    return(!_currentRowId.equals(getDirHandler(moveForward).getEndRowId()));
   }
-
+  
   /**
    * Moves to the first row (as defined by the cursor) where the given column
    * has the given value.  This may be more efficient on some cursors than
@@ -471,12 +528,21 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
   /**
    * Returns the given column from the current row.
    */
+  @SuppressWarnings("foo")
   public Object getCurrentRowValue(Column column)
     throws IOException
   {
     return _table.getRowValue(_rowState, _currentRowId, column);
   }
 
+  /**
+   * 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();
+  }
+  
   /**
    * 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
@@ -552,31 +618,44 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
    */
   private static class TableScanCursor extends Cursor
   {
-    /** ScanDirHandler for forward iteration */
+    /** ScanDirHandler for forward traversal */
     private final ScanDirHandler _forwardDirHandler =
       new ForwardScanDirHandler();
-    /** ScanDirHandler for backward iteration */
+    /** ScanDirHandler for backward traversal */
     private final ScanDirHandler _reverseDirHandler =
       new ReverseScanDirHandler();
-    /** Iterator over the pages that this table owns */
-    private final UsageMap.PageIterator _ownedPagesIterator;
+    /** Cursor over the pages that this table owns */
+    private final UsageMap.PageCursor _ownedPagesCursor;
     
     private TableScanCursor(Table table) {
       super(table, RowId.FIRST_ROW_ID, RowId.LAST_ROW_ID);
-      _ownedPagesIterator = table.getOwnedPagesIterator();
+      _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) {
-      _ownedPagesIterator.reset(moveForward);
+      _ownedPagesCursor.reset(moveForward);
       super.reset(moveForward);
     }
 
+    @Override
+    protected void restorePreviousPosition()
+      throws IOException
+    {
+      super.restorePreviousPosition();
+      _ownedPagesCursor.setCurrentPage(getCurrentRowId().getPageNumber());
+    }
+    
     /**
      * Position the buffer at the next row in the table
      * @return a ByteBuffer narrowed to the next row, or null if none
@@ -591,17 +670,15 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
       // figure out how many rows are left on this page so we can find the
       // next row
       Table.positionAtRowHeader(rowState, currentRowId);
-      int rowInc = handler.getRowIncrement();
       int currentRowNumber = currentRowId.getRowNumber();
     
       // loop until we find the next valid row or run out of pages
       while(true) {
 
-        currentRowNumber += rowInc;
+        currentRowNumber = handler.getAnotherRowNumber(currentRowNumber);
         currentRowId = new RowId(currentRowId.getPageNumber(),
                                  currentRowNumber);
-        ByteBuffer rowBuffer =
-          Table.positionAtRowHeader(rowState, currentRowId);
+        Table.positionAtRowHeader(rowState, currentRowId);
         
         if(!rowState.isValid()) {
           
@@ -633,7 +710,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
      * cursor logic from value storage.
      */
     private abstract class ScanDirHandler extends DirHandler {
-      public abstract int getRowIncrement();
+      public abstract int getAnotherRowNumber(int curRowNumber);
       public abstract int getAnotherPageNumber();
       public abstract int getInitialRowNumber(int rowsOnPage);
     }
@@ -642,18 +719,23 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
      * Handles moving the table scan cursor forward.
      */
     private final class ForwardScanDirHandler extends ScanDirHandler {
+      @Override
       public RowId getBeginningRowId() {
         return getFirstRowId();
       }
+      @Override
       public RowId getEndRowId() {
         return getLastRowId();
       }
-      public int getRowIncrement() {
-        return 1;
+      @Override
+      public int getAnotherRowNumber(int curRowNumber) {
+        return curRowNumber + 1;
       }
+      @Override
       public int getAnotherPageNumber() {
-        return _ownedPagesIterator.getNextPage();
+        return _ownedPagesCursor.getNextPage();
       }
+      @Override
       public int getInitialRowNumber(int rowsOnPage) {
         return -1;
       }
@@ -663,18 +745,23 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
      * Handles moving the table scan cursor backward.
      */
     private final class ReverseScanDirHandler extends ScanDirHandler {
+      @Override
       public RowId getBeginningRowId() {
         return getLastRowId();
       }
+      @Override
       public RowId getEndRowId() {
         return getFirstRowId();
       }
-      public int getRowIncrement() {
-        return -1;
+      @Override
+      public int getAnotherRowNumber(int curRowNumber) {
+        return curRowNumber - 1;
       }
+      @Override
       public int getAnotherPageNumber() {
-        return _ownedPagesIterator.getPreviousPage();
+        return _ownedPagesCursor.getPreviousPage();
       }
+      @Override
       public int getInitialRowNumber(int rowsOnPage) {
         return rowsOnPage;
       }
index 1ad512c06ced5f85c8ccdc90f5e09716a3fc24e3..da6b0b2b467ca0befd023027f1fd35070e99be16 100644 (file)
@@ -1260,7 +1260,11 @@ public class Index implements Comparable<Index> {
     private EntryIterator() {
       reset();
     }
-    
+
+    public boolean isUpToDate() {
+      return(Index.this._modCount == _lastModCount);
+    }
+        
     public void reset() {
       beforeFirst();
     }
@@ -1280,7 +1284,7 @@ public class Index implements Comparable<Index> {
     }
 
     private void resyncIndex() {
-      if(Index.this._modCount != _lastModCount) {
+      if(!isUpToDate()) {
         if(_nextEntryIdx == 0) {
           // we were at the beginning of the list
           _nextEntry = _entries.get(_nextEntryIdx);
index 41512f87bb38448b343eb3e3f350619a5a002483..bb4b491f7637c9fd1371d2078cd1a806429a6d79 100644 (file)
@@ -54,7 +54,7 @@ public class RowId implements Comparable<RowId>
    * Returns {@code true} if this rowId potentially represents an actual row
    * of data, {@code false} otherwise.
    */
-  public boolean isValidRowId() {
+  public boolean isValid() {
     return((getRowNumber() >= 0) && (getPageNumber() >= 0));
   }
 
index fff2f77c94e198739c3a44fd1174406c030f730f..7a3df79b7cb6bb4a2b06e5fcb875e3265ed18a40 100644 (file)
@@ -191,8 +191,8 @@ public class Table
     return new RowState(true);
   }
 
-  protected UsageMap.PageIterator getOwnedPagesIterator() {
-    return _ownedPages.iterator();
+  protected UsageMap.PageCursor getOwnedPagesCursor() {
+    return _ownedPages.cursor();
   }
   
   /**
@@ -266,21 +266,17 @@ public class Table
   public void deleteCurrentRow() throws IOException {
     _cursor.deleteCurrentRow();
   }
-  
+
   /**
    * Delete the row on which the given rowState is currently positioned.
    */
   public void deleteRow(RowState rowState, RowId rowId) throws IOException {
+    requireValidRowId(rowId);
+    
     // ensure that the relevant row state is up-to-date
     ByteBuffer rowBuffer = positionAtRowHeader(rowState, rowId);
 
-    if(!rowState.isValid()) {
-      throw new IllegalArgumentException(
-          "Given rowId is invalid for this table " + rowId);
-    }
-    if(rowState.isDeleted()) {
-      throw new IllegalStateException("Deleting already deleted row");
-    }
+    requireNonDeletedRow(rowState, rowId);
     
     // delete flag always gets set in the "header" row (even if data is on
     // overflow row)
@@ -333,18 +329,11 @@ public class Table
       throw new IllegalArgumentException(
           "Given column " + column + " is not from this table");
     }
+    requireValidRowId(rowId);
     
     // position at correct row
     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
-    if(!rowState.isValid()) {
-      // this was a bogus rowId
-      throw new IllegalArgumentException(
-          "Given rowId is not valid for this table " + rowId);
-    }
-    if(rowState.isDeleted()) {
-      // note, row state will indicate that row was deleted
-      return null;
-    }
+    requireNonDeletedRow(rowState, rowId);
     
     Object value = getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column);
 
@@ -365,17 +354,11 @@ public class Table
       RowState rowState, RowId rowId, Collection<String> columnNames)
     throws IOException
   {
+    requireValidRowId(rowId);
+
     // position at correct row
     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
-    if(!rowState.isValid()) {
-      // this was a bogus rowId
-      throw new IllegalArgumentException(
-          "Given rowId is not valid for this table " + rowId);
-    }
-    if(rowState.isDeleted()) {
-      // note, row state will indicate that row was deleted
-      return null;
-    }
+    requireNonDeletedRow(rowState, rowId);
 
     return getRow(rowState, rowBuffer, getRowNullMask(rowBuffer), _columns,
                   columnNames);
@@ -1061,10 +1044,12 @@ public class Table
 
     // find last data page (Not bothering to check other pages for free
     // space.)
-    for(UsageMap.PageIterator revPageIter = _ownedPages.iteratorAtEnd();
-        revPageIter.hasPreviousPage(); )
-    {
-      int tmpPageNumber = revPageIter.getPreviousPage();
+    UsageMap.PageCursor revPageCursor = _ownedPages.cursorAtEnd();
+    while(true) {
+      int tmpPageNumber = revPageCursor.getPreviousPage();
+      if(tmpPageNumber < 0) {
+        break;
+      }
       getPageChannel().readPage(dataPage, tmpPageNumber);
       if(dataPage.get() == PageTypes.DATA) {
         // found last data page
@@ -1384,7 +1369,7 @@ public class Table
    * Returns the row count for the current page.  If the page is invalid
    * ({@code null}) or the page is not a DATA page, 0 is returned.
    */
-  public static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
+  private static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
     throws IOException
   {
     int rowsOnPage = 0;
@@ -1395,16 +1380,25 @@ public class Table
   }
 
   /**
-   * Returns {@code true} if the row is marked as deleted, {@code false}
-   * otherwise.
+   * @throws IllegalStateException if the given rowId is invalid
    */
-  public static boolean isDeletedRow(ByteBuffer rowBuffer, int rowNum,
-                                     JetFormat format)
-    throws IOException
-  {
-    // note, we don't use findRowStart here cause we need the unmasked value
-    return isDeletedRow(
-        rowBuffer.getShort(Table.getRowStartOffset(rowNum, format)));
+  private static void requireValidRowId(RowId rowId) {
+    if(!rowId.isValid()) {
+      throw new IllegalArgumentException("Given rowId is invalid: " + rowId);
+    }
+  }
+  
+  /**
+   * @throws IllegalStateException if the given row is invalid or deleted
+   */
+  private static void requireNonDeletedRow(RowState rowState, RowId rowId) {
+    if(!rowState.isValid()) {
+      throw new IllegalArgumentException(
+          "Given rowId is invalid for this table: " + rowId);
+    }
+    if(rowState.isDeleted()) {
+      throw new IllegalStateException("Row is deleted: " + rowId);
+    }
   }
   
   public static boolean isDeletedRow(short rowStart) {
@@ -1513,8 +1507,12 @@ public class Table
       }
     }
 
+    public boolean isUpToDate() {
+      return(Table.this._modCount == _lastModCount);
+    }
+    
     private void checkForModification() {
-      if(Table.this._modCount != _lastModCount) {
+      if(!isUpToDate()) {
         reset();
         _headerRowBufferH.invalidate();
         _overflowRowBufferH.invalidate();
@@ -1641,7 +1639,7 @@ public class Table
     {
       // this should never see modifications because it only happens within
       // the positionAtRowData method
-      if(_lastModCount != Table.this._modCount) {
+      if(!isUpToDate()) {
         throw new IllegalStateException("Table modified while searching?");
       }
       if(_rowStatus != RowStatus.OVERFLOW) {
index b8121bd773f72eeb9d84444954311fa69f81e615..ee25d95612f77359a40aed24109957d150b773dc 100644 (file)
@@ -41,7 +41,6 @@ import org.apache.commons.logging.LogFactory;
  */
 public class UsageMap
 {
-  
   private static final Log LOG = LogFactory.getLog(UsageMap.class);
   
   /** Inline map type */
@@ -49,6 +48,9 @@ public class UsageMap
   /** Reference map type, for maps that are too large to fit inline */
   public static final byte MAP_TYPE_REFERENCE = 0x1;
 
+  /** bit index value for an invalid page number */
+  private static final int INVALID_BIT_INDEX = -1;
+  
   /** owning database */
   private final Database _database;
   /** Page number of the map table declaration */
@@ -65,9 +67,9 @@ public class UsageMap
   private BitSet _pageNumbers = new BitSet();
   /** Buffer that contains the usage map table declaration page */
   private final ByteBuffer _tableBuffer;
-  /** modification count on the usage map, used to keep the iterators in
+  /** modification count on the usage map, used to keep the cursors in
       sync */
-  private int _modCount = 0;
+  private int _modCount;
   /** the current handler implementation for reading/writing the specific
       usage map type.  note, this may change over time. */
   private Handler _handler;
@@ -142,14 +144,14 @@ public class UsageMap
     }
   }
   
-  public PageIterator iterator() {
-    return new PageIterator();
+  public PageCursor cursor() {
+    return new PageCursor();
   }
 
-  public PageIterator iteratorAtEnd() {
-    PageIterator iterator = new PageIterator();
-    iterator.afterLast();
-    return iterator;
+  public PageCursor cursorAtEnd() {
+    PageCursor cursor = new PageCursor();
+    cursor.afterLast();
+    return cursor;
   }
   
   protected short getRowStart() {
@@ -230,8 +232,8 @@ public class UsageMap
   }
 
   protected int pageNumberToBitIndex(int pageNumber) {
-    return((pageNumber != PageChannel.INVALID_PAGE_NUMBER) ?
-           (pageNumber - _startPage) : -1);
+    return((pageNumber >= 0) ? (pageNumber - _startPage) :
+           INVALID_BIT_INDEX);
   }
 
   protected void clearTableAndPages()
@@ -372,11 +374,13 @@ public class UsageMap
   
   public String toString() {
     StringBuilder builder = new StringBuilder("page numbers: [");
-    for(PageIterator iter = iterator(); iter.hasNextPage(); ) {
-      builder.append(iter.getNextPage());
-      if(iter.hasNextPage()) {
-        builder.append(", ");
+    PageCursor pCursor = cursor();
+    while(true) {
+      int nextPage = pCursor.getNextPage();
+      if(nextPage < 0) {
+        break;
       }
+      builder.append(nextPage).append(", ");
     }
     builder.append("]");
     return builder.toString();
@@ -693,29 +697,27 @@ public class UsageMap
         (pageIndex * 4);
     }
   }
-  
-  
+
   /**
-   * Utility class to iterate over the pages in the UsageMap.  Note, since the
-   * iterators hold on to page numbers, they should stay valid even as the
-   * usage map handlers shift around the bits.
+   * Utility class to traverse over the pages in the UsageMap.  Remains valid
+   * in the face of usage map modifications.
    */
-  public class PageIterator
+  public class PageCursor
   {
-    /** handler for moving the page iterator forward */
+    /** handler for moving the page cursor forward */
     private final DirHandler _forwardDirHandler = new ForwardDirHandler();
-    /** handler for moving the page iterator backward */
+    /** handler for moving the page cursor backward */
     private final DirHandler _reverseDirHandler = new ReverseDirHandler();
-    /** the next used page number */
-    private int _nextPageNumber;
+    /** the current used page number */
+    private int _curPageNumber;
     /** the previous used page number */
     private int _prevPageNumber;
     /** the last read modification count on the UsageMap.  we track this so
-        that the iterator can detect updates to the usage map while iterating
+        that the cursor can detect updates to the usage map while traversing
         and act accordingly */
     private int _lastModCount;
 
-    private PageIterator() {
+    private PageCursor() {
       reset();
     }
 
@@ -725,58 +727,47 @@ public class UsageMap
     private DirHandler getDirHandler(boolean moveForward) {
       return (moveForward ? _forwardDirHandler : _reverseDirHandler);
     }
-    
+
     /**
-     * @return {@code true} if there is another valid page after the current
-     *         page, {@code false} otherwise.
+     * Returns {@code true} if this cursor is up-to-date with respect to its
+     * usage map.
      */
-    public final boolean hasNextPage() {
-      return hasAnotherPage(true);
-    }      
-    
+    public boolean isUpToDate() {
+      return(UsageMap.this._modCount == _lastModCount);
+    }    
+
     /**
-     * @return {@code true} if there is another valid page before the current
-     *         page, {@code false} otherwise.
+     * Returns the current page number.
      */
-    public final boolean hasPreviousPage() {
-      return hasAnotherPage(false);
-    }      
+    public int getCurrentPage() {
+      return _curPageNumber;
+    }
 
-    private boolean hasAnotherPage(boolean moveForward) {
-      DirHandler handler = getDirHandler(moveForward);
-      int curPageNumber = handler.getCurrentPageNumber();
-      int otherPageNumber = handler.getOtherPageNumber();
-      
-      if((curPageNumber == PageChannel.INVALID_PAGE_NUMBER) &&
-         (_lastModCount != UsageMap.this._modCount)) {
-        // recheck the last page, in case more showed up
-        if(otherPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
-          // we were at the beginning
-          reset(moveForward);
-        } else {
-          // we were at the end
-          _lastModCount = UsageMap.this._modCount;
-          handler.setCurrentPageNumber(
-              handler.getAnotherPageNumber(otherPageNumber));
-        }
-        curPageNumber = handler.getCurrentPageNumber();
+    /**
+     * Resets the cursor to the given page number.
+     */
+    public void setCurrentPage(int curPageNumber) {
+      if(curPageNumber < UsageMap.this.getFirstPageNumber()) {
+        curPageNumber = RowId.FIRST_PAGE_NUMBER;
+      } else if(curPageNumber > UsageMap.this.getLastPageNumber()) {
+        curPageNumber = RowId.LAST_PAGE_NUMBER;
       }
-      return(curPageNumber != PageChannel.INVALID_PAGE_NUMBER);
-    }      
+      restorePosition(curPageNumber);
+    }
     
     /**
      * @return valid page number if there was another page to read,
-     *         {@link PageChannel#INVALID_PAGE_NUMBER} otherwise
+     *         {@link RowId#LAST_PAGE_NUMBER} otherwise
      */
-    public final int getNextPage() {
+    public int getNextPage() {
       return getAnotherPage(true);
     }
 
     /**
      * @return valid page number if there was another page to read,
-     *         {@link PageChannel#INVALID_PAGE_NUMBER} otherwise
+     *         {@link RowId#FIRST_PAGE_NUMBER} otherwise
      */
-    public final int getPreviousPage() {
+    public int getPreviousPage() {
       return getAnotherPage(false);
     }
 
@@ -784,18 +775,23 @@ public class UsageMap
      * Gets another page in the given direction, returning the current page.
      */
     private int getAnotherPage(boolean moveForward) {
-      if (hasAnotherPage(moveForward)) {
-        _lastModCount = UsageMap.this._modCount;
-        DirHandler handler = getDirHandler(moveForward);
-        int anotherPage = handler.getCurrentPageNumber();
-        handler.setOtherPageNumber(anotherPage);
-        handler.setCurrentPageNumber(
-            handler.getAnotherPageNumber(anotherPage));
-        return anotherPage;
+      DirHandler handler = getDirHandler(moveForward);
+      if(_curPageNumber == handler.getEndPageNumber()) {
+        if(!isUpToDate()) {
+          restorePosition(_prevPageNumber);
+          // drop through and retry moving to another page
+        } else {
+          // at end, no more
+          return _curPageNumber;
+        }
       }
-      return PageChannel.INVALID_PAGE_NUMBER;
+
+      _prevPageNumber = _curPageNumber;
+      _curPageNumber = handler.getAnotherPageNumber(_curPageNumber);
+      _lastModCount = UsageMap.this._modCount;
+      return _curPageNumber;
     }
-    
+
     /**
      * After calling this method, getNextPage will return the first page in
      * the map
@@ -821,73 +817,76 @@ public class UsageMap
     }
 
     /**
-     * Resets this page iterator for iterating the given direction.
+     * Resets this page cursor for traversing the given direction.
      */
     protected void reset(boolean moveForward) {
       _lastModCount = UsageMap.this._modCount;
-      getDirHandler(moveForward).reset();
+      _curPageNumber = getDirHandler(moveForward).getBeginningPageNumber();
+      _prevPageNumber = _curPageNumber;
     }
 
     /**
-     * Handles moving the iterator in a given direction.  Separates iterator
+     * Restores a previous position for the cursor.
+     */
+    private void restorePosition(int curPageNumber) {
+      _prevPageNumber = _curPageNumber;
+      _curPageNumber = curPageNumber;
+      _lastModCount = UsageMap.this._modCount;
+    }
+    
+    /**
+     * Handles moving the cursor in a given direction.  Separates cursor
      * logic from value storage.
      */
     private abstract class DirHandler {
-      public abstract int getCurrentPageNumber();
-      public abstract void setCurrentPageNumber(int newPageNumber);
-      public abstract int getOtherPageNumber();
-      public abstract void setOtherPageNumber(int newPageNumber);
       public abstract int getAnotherPageNumber(int curPageNumber);
-      public abstract void reset();
+      public abstract int getBeginningPageNumber();
+      public abstract int getEndPageNumber();
     }
         
     /**
-     * Handles moving the iterator forward.
+     * Handles moving the cursor forward.
      */
     private final class ForwardDirHandler extends DirHandler {
-      public int getCurrentPageNumber() {
-        return _nextPageNumber;
-      }
-      public void setCurrentPageNumber(int newPageNumber) {
-        _nextPageNumber = newPageNumber;
-      }
-      public int getOtherPageNumber() {
-        return _prevPageNumber;
-      }
-      public void setOtherPageNumber(int newPageNumber) {
-        _prevPageNumber = newPageNumber;
-      }
+      @Override
       public int getAnotherPageNumber(int curPageNumber) {
-        return UsageMap.this.getNextPageNumber(curPageNumber);
+        if(curPageNumber == RowId.FIRST_PAGE_NUMBER) {
+          return UsageMap.this.getFirstPageNumber();
+        }
+        int anotherPageNumber = UsageMap.this.getNextPageNumber(curPageNumber);
+        return ((anotherPageNumber >= 0) ? anotherPageNumber :
+                RowId.LAST_PAGE_NUMBER);
+      }
+      @Override
+      public int getBeginningPageNumber() {
+        return RowId.FIRST_PAGE_NUMBER;
       }
-      public void reset() {
-        _nextPageNumber = UsageMap.this.getFirstPageNumber();
-        _prevPageNumber = PageChannel.INVALID_PAGE_NUMBER;
+      @Override
+      public int getEndPageNumber() {
+        return RowId.LAST_PAGE_NUMBER;
       }
     }
         
     /**
-     * Handles moving the iterator backward.
+     * Handles moving the cursor backward.
      */
     private final class ReverseDirHandler extends DirHandler {
-      public int getCurrentPageNumber() {
-        return _prevPageNumber;
-      }
-      public void setCurrentPageNumber(int newPageNumber) {
-        _prevPageNumber = newPageNumber;
-      }
-      public int getOtherPageNumber() {
-        return _nextPageNumber;
-      }
-      public void setOtherPageNumber(int newPageNumber) {
-        _nextPageNumber = newPageNumber;
-      }
+      @Override
       public int getAnotherPageNumber(int curPageNumber) {
-        return UsageMap.this.getPrevPageNumber(curPageNumber);
+        if(curPageNumber == RowId.LAST_PAGE_NUMBER) {
+          return UsageMap.this.getLastPageNumber();
+        }
+        int anotherPageNumber = UsageMap.this.getPrevPageNumber(curPageNumber);
+        return ((anotherPageNumber >= 0) ? anotherPageNumber :
+                RowId.FIRST_PAGE_NUMBER);
+      }
+      @Override
+      public int getBeginningPageNumber() {
+        return RowId.LAST_PAGE_NUMBER;
       }
-      public void reset() {
-        _nextPageNumber = PageChannel.INVALID_PAGE_NUMBER;
-        _prevPageNumber = UsageMap.this.getLastPageNumber();
+      @Override
+      public int getEndPageNumber() {
+        return RowId.FIRST_PAGE_NUMBER;
       }
     }
         
index ed3e5c5fdfa95242078a5c0eb180dde953bbade8..dc6cc017243158e75bbcc94e4234a1fd9f3e2a1b 100644 (file)
@@ -155,5 +155,77 @@ public class CursorTest extends TestCase {
     db.close();
   }
 
+  public void testLiveAddition() throws Exception {
+    Database db = createTestTable();
+
+    Table table = db.getTable("test");
+
+    Cursor cursor1 = Cursor.createCursor(table);
+    Cursor cursor2 = Cursor.createCursor(table);
+    cursor1.skipNextRows(11);
+    cursor2.skipNextRows(11);
+
+    assertTrue(cursor1.isAfterLast());
+    assertTrue(cursor2.isAfterLast());
+
+    int newRowNum = 11;
+    table.addRow(newRowNum, "data" + newRowNum);
+    Map<String,Object> expectedRow = 
+      createExpectedRow("id", newRowNum, "value", "data" + newRowNum);
+
+    assertFalse(cursor1.isAfterLast());
+    assertFalse(cursor2.isAfterLast());
+
+    assertEquals(expectedRow, cursor1.getCurrentRow());
+    assertEquals(expectedRow, cursor2.getCurrentRow());
+    assertFalse(cursor1.moveToNextRow());
+    assertFalse(cursor2.moveToNextRow());
+    assertTrue(cursor1.isAfterLast());
+    assertTrue(cursor2.isAfterLast());
+    
+    db.close();
+  }
+
+  public void testLiveDeletion() throws Exception {
+    Database db = createTestTable();
+
+    Table table = db.getTable("test");
+
+    Cursor cursor1 = Cursor.createCursor(table);
+    Cursor cursor2 = Cursor.createCursor(table);
+    Cursor cursor3 = Cursor.createCursor(table);
+    Cursor cursor4 = Cursor.createCursor(table);
+    cursor1.skipNextRows(2);
+    cursor2.skipNextRows(3);
+    cursor3.skipNextRows(3);
+    cursor4.skipNextRows(4);
+
+    Map<String,Object> expectedPrevRow =
+      createExpectedRow("id", 1, "value", "data" + 1);
+    Map<String,Object> expectedDeletedRow =
+      createExpectedRow("id", 2, "value", "data" + 2);
+    Map<String,Object> expectedNextRow =
+      createExpectedRow("id", 3, "value", "data" + 3);
+    
+    assertEquals(expectedDeletedRow, cursor2.getCurrentRow());
+    assertEquals(expectedDeletedRow, cursor3.getCurrentRow());
+    
+    assertFalse(cursor2.isCurrentRowDeleted());
+    assertFalse(cursor3.isCurrentRowDeleted());
+
+    cursor2.deleteCurrentRow();
+
+    assertTrue(cursor2.isCurrentRowDeleted());
+    assertTrue(cursor3.isCurrentRowDeleted());
+
+    assertEquals(expectedNextRow, cursor1.getNextRow());
+    assertEquals(expectedNextRow, cursor2.getNextRow());
+    assertEquals(expectedNextRow, cursor3.getNextRow());
+    
+    assertEquals(expectedPrevRow, cursor3.getPreviousRow());
+    
+    db.close();
+  }
+
   
 }