From 2c900e17e9bd5299ecbc7c4bc19eff06583e1069 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Mon, 26 Nov 2007 20:18:09 +0000 Subject: [PATCH] make usagemap cursor work similarly to Cursor; handle live additions and deletions better git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@183 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../healthmarketscience/jackcess/Cursor.java | 137 +++++++++-- .../healthmarketscience/jackcess/Index.java | 8 +- .../healthmarketscience/jackcess/RowId.java | 2 +- .../healthmarketscience/jackcess/Table.java | 86 ++++--- .../jackcess/UsageMap.java | 229 +++++++++--------- .../jackcess/CursorTest.java | 72 ++++++ 6 files changed, 347 insertions(+), 187 deletions(-) diff --git a/src/java/com/healthmarketscience/jackcess/Cursor.java b/src/java/com/healthmarketscience/jackcess/Cursor.java index be6e5e7..5ef2c24 100644 --- a/src/java/com/healthmarketscience/jackcess/Cursor.java +++ b/src/java/com/healthmarketscience/jackcess/Cursor.java @@ -34,6 +34,8 @@ public abstract class Cursor implements Iterable> 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> _firstRowId = firstRowId; _lastRowId = lastRowId; _currentRowId = firstRowId; + _previousRowId = firstRowId; } /** @@ -159,16 +162,26 @@ public abstract class Cursor implements Iterable> * 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> 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> */ protected void reset(boolean moveForward) { _currentRowId = getDirHandler(moveForward).getBeginningRowId(); + _previousRowId = _currentRowId; _rowState.reset(); } @@ -350,16 +365,58 @@ public abstract class Cursor implements Iterable> { 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> /** * 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> */ 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> // 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> * 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> * 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> * 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; } diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index 1ad512c..da6b0b2 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -1260,7 +1260,11 @@ public class Index implements Comparable { private EntryIterator() { reset(); } - + + public boolean isUpToDate() { + return(Index.this._modCount == _lastModCount); + } + public void reset() { beforeFirst(); } @@ -1280,7 +1284,7 @@ public class Index implements Comparable { } 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); diff --git a/src/java/com/healthmarketscience/jackcess/RowId.java b/src/java/com/healthmarketscience/jackcess/RowId.java index 41512f8..bb4b491 100644 --- a/src/java/com/healthmarketscience/jackcess/RowId.java +++ b/src/java/com/healthmarketscience/jackcess/RowId.java @@ -54,7 +54,7 @@ public class RowId implements Comparable * 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)); } diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java index fff2f77..7a3df79 100644 --- a/src/java/com/healthmarketscience/jackcess/Table.java +++ b/src/java/com/healthmarketscience/jackcess/Table.java @@ -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 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) { diff --git a/src/java/com/healthmarketscience/jackcess/UsageMap.java b/src/java/com/healthmarketscience/jackcess/UsageMap.java index b8121bd..ee25d95 100644 --- a/src/java/com/healthmarketscience/jackcess/UsageMap.java +++ b/src/java/com/healthmarketscience/jackcess/UsageMap.java @@ -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; } } diff --git a/test/src/java/com/healthmarketscience/jackcess/CursorTest.java b/test/src/java/com/healthmarketscience/jackcess/CursorTest.java index ed3e5c5..dc6cc01 100644 --- a/test/src/java/com/healthmarketscience/jackcess/CursorTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/CursorTest.java @@ -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 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 expectedPrevRow = + createExpectedRow("id", 1, "value", "data" + 1); + Map expectedDeletedRow = + createExpectedRow("id", 2, "value", "data" + 2); + Map 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(); + } + } -- 2.39.5