From 8ab1652512c8b49a0ad222f99276c1fa2ddc59d3 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Tue, 5 Mar 2013 13:34:47 +0000 Subject: [PATCH] move cursor impls to CursorImpl and IndexCursorImpl git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jackcess-2@674 f203690c-595d-4dc9-a70b-905162fa7fd2 --- TODO.txt | 1 + .../healthmarketscience/jackcess/Cursor.java | 1297 +--------------- .../jackcess/CursorBuilder.java | 16 +- .../jackcess/CursorImpl.java | 1363 +++++++++++++++++ .../jackcess/DatabaseImpl.java | 48 +- .../jackcess/ExportUtil.java | 10 +- .../jackcess/FKEnforcer.java | 2 +- .../jackcess/IndexCursor.java | 528 +------ .../jackcess/IndexCursorImpl.java | 545 +++++++ .../jackcess/IndexData.java | 10 +- .../healthmarketscience/jackcess/Joiner.java | 8 +- .../jackcess/TableImpl.java | 6 +- .../jackcess/UsageMap.java | 8 +- .../jackcess/complex/ComplexColumnInfo.java | 8 +- 14 files changed, 2060 insertions(+), 1790 deletions(-) create mode 100644 src/java/com/healthmarketscience/jackcess/CursorImpl.java create mode 100644 src/java/com/healthmarketscience/jackcess/IndexCursorImpl.java diff --git a/TODO.txt b/TODO.txt index ba829a4..e630bde 100644 --- 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? diff --git a/src/java/com/healthmarketscience/jackcess/Cursor.java b/src/java/com/healthmarketscience/jackcess/Cursor.java index fa586fd..e19d4c2 100644 --- a/src/java/com/healthmarketscience/jackcess/Cursor.java +++ b/src/java/com/healthmarketscience/jackcess/Cursor.java @@ -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> -{ - 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * Warning, this method always 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 findRow(TableImpl table, - Map 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. - *

- * 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. - *

- * Warning, this method always 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 findRow(TableImpl table, IndexImpl index, - Map 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. - *

- * 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> +{ - 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> * 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> * 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 afterLast @@ -459,9 +133,7 @@ public abstract class Cursor implements Iterable> * @throws IllegalStateException if an IOException is thrown by one of the * operations, the actual exception will be contained within */ - public Iterable> reverseIterable() { - return reverseIterable(null); - } + public Iterable> reverseIterable(); /** * Returns an Iterable whose iterator() method calls afterLast @@ -473,15 +145,8 @@ public abstract class Cursor implements Iterable> * operations, the actual exception will be contained within */ public Iterable> reverseIterable( - final Collection columnNames) - { - return new Iterable>() { - public Iterator> iterator() { - return new RowIterator(columnNames, MOVE_REVERSE); - } - }; - } - + Collection columnNames); + /** * Calls beforeFirst 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> * @throws IllegalStateException if an IOException is thrown by one of the * operations, the actual exception will be contained within */ - public Iterator> iterator() - { - return iterator(null); - } - + public Iterator> 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> * operations, the actual exception will be contained within */ public Iterable> iterable( - final Collection columnNames) - { - return new Iterable>() { - public Iterator> iterator() { - return Cursor.this.iterator(columnNames); - } - }; - } - + Collection columnNames); + /** * Calls beforeFirst 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> * @throws IllegalStateException if an IOException is thrown by one of the * operations, the actual exception will be contained within */ - public Iterator> iterator(Collection columnNames) - { - return new RowIterator(columnNames, MOVE_FORWARD); - } + public Iterator> iterator(Collection columnNames); /** * Returns an Iterable whose iterator() method returns the result of a call @@ -531,11 +183,8 @@ public abstract class Cursor implements Iterable> * operations, the actual exception will be contained within */ public Iterable> columnMatchIterable( - ColumnImpl columnPattern, Object valuePattern) - { - return columnMatchIterable(null, columnPattern, valuePattern); - } - + Column columnPattern, Object valuePattern); + /** * Calls beforeFirst 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> * operations, the actual exception will be contained within */ public Iterator> 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> columnMatchIterable( - final Collection columnNames, - final ColumnImpl columnPattern, final Object valuePattern) - { - return new Iterable>() { - public Iterator> iterator() { - return Cursor.this.columnMatchIterator( - columnNames, columnPattern, valuePattern); - } - }; - } - + Column columnPattern, Object valuePattern); + /** * Calls beforeFirst 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> * operations, the actual exception will be contained within */ public Iterator> columnMatchIterator( - Collection columnNames, ColumnImpl columnPattern, Object valuePattern) - { - return new ColumnMatchIterator(columnNames, columnPattern, valuePattern); - } + Collection 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> * operations, the actual exception will be contained within */ public Iterable> rowMatchIterable( - Map rowPattern) - { - return rowMatchIterable(null, rowPattern); - } - + Map rowPattern); + /** * Calls beforeFirst 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> * operations, the actual exception will be contained within */ public Iterator> rowMatchIterator( - Map rowPattern) - { - return rowMatchIterator(null, rowPattern); - } - + Map 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> * operations, the actual exception will be contained within */ public Iterable> rowMatchIterable( - final Collection columnNames, - final Map rowPattern) - { - return new Iterable>() { - public Iterator> iterator() { - return Cursor.this.rowMatchIterator( - columnNames, rowPattern); - } - }; - } - + Collection columnNames, + Map rowPattern); + /** * Calls beforeFirst 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> * operations, the actual exception will be contained within */ public Iterator> rowMatchIterator( - Collection columnNames, Map rowPattern) - { - return new RowMatchIterator(columnNames, rowPattern); - } + Collection columnNames, Map 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 getNextRow() throws IOException { - return getNextRow(null); - } + public Map getNextRow() throws IOException; /** * Moves to the next row in the table and returns it. @@ -680,19 +282,14 @@ public abstract class Cursor implements Iterable> * {@code null} if no next row is found */ public Map getNextRow(Collection 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 getPreviousRow() throws IOException { - return getPreviousRow(null); - } + public Map getPreviousRow() throws IOException; /** * Moves to the previous row in the table and returns it. @@ -701,144 +298,21 @@ public abstract class Cursor implements Iterable> * {@code null} if no previous row is found */ public Map getPreviousRow(Collection 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 getAnotherRow(Collection 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> * @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> * @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 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> * @return {@code true} if a valid row was found with the given values, * {@code false} if no row was found */ - public boolean findFirstRow(Map 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 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> * @return {@code true} if a valid row was found with the given values, * {@code false} if no row was found */ - public boolean findNextRow(Map 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 rowPattern) throws IOException; /** * Returns {@code true} if the current row matches the given pattern. @@ -993,615 +385,84 @@ public abstract class Cursor implements Iterable> * @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 rowPattern) - throws IOException - { - Map row = getCurrentRow(rowPattern.keySet()); - - if(rowPattern.size() != row.size()) { - return false; - } - - for(Map.Entry 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. - *

- * 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. - *

- * 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 rowPattern) - throws IOException - { - while(moveToNextRow()) { - if(currentRowMatches(rowPattern)) { - return true; - } - } - return false; - } + public boolean currentRowMatches(Map 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 getCurrentRow() - throws IOException - { - return getCurrentRow(null); - } + public Map 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 getCurrentRow(Collection 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> - { - protected final Collection _columnNames; - protected Boolean _hasNext; - protected boolean _validRow; - - protected BaseIterator(Collection 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 next() { - if(!hasNext()) { - throw new NoSuchElementException(); - } - try { - Map 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 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 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 _rowPattern; - - private RowMatchIterator(Collection columnNames, - Map 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(); } - + } diff --git a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java index 3a56659..5b84534 100644 --- a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java +++ b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java @@ -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 index 0000000..582c8f0 --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/CursorImpl.java @@ -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. + *

+ * 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}. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * Warning, this method always 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 findRow(TableImpl table, + Map 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. + *

+ * 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. + *

+ * Warning, this method always 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 findRow(TableImpl table, IndexImpl index, + Map 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. + *

+ * 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> reverseIterable() { + return reverseIterable(null); + } + + public Iterable> reverseIterable( + final Collection columnNames) + { + return new Iterable>() { + public Iterator> iterator() { + return new RowIterator(columnNames, MOVE_REVERSE); + } + }; + } + + public Iterator> iterator() + { + return iterator(null); + } + + public Iterable> iterable( + final Collection columnNames) + { + return new Iterable>() { + public Iterator> iterator() { + return CursorImpl.this.iterator(columnNames); + } + }; + } + + public Iterator> iterator(Collection columnNames) + { + return new RowIterator(columnNames, MOVE_FORWARD); + } + + public Iterable> columnMatchIterable( + Column columnPattern, Object valuePattern) + { + return columnMatchIterable((ColumnImpl)columnPattern, valuePattern); + } + + public Iterable> columnMatchIterable( + ColumnImpl columnPattern, Object valuePattern) + { + return columnMatchIterable(null, columnPattern, valuePattern); + } + + public Iterator> columnMatchIterator( + Column columnPattern, Object valuePattern) + { + return columnMatchIterator((ColumnImpl)columnPattern, valuePattern); + } + + public Iterator> columnMatchIterator( + ColumnImpl columnPattern, Object valuePattern) + { + return columnMatchIterator(null, columnPattern, valuePattern); + } + + public Iterable> columnMatchIterable( + Collection columnNames, + Column columnPattern, Object valuePattern) + { + return columnMatchIterable(columnNames, (ColumnImpl)columnPattern, + valuePattern); + } + + public Iterable> columnMatchIterable( + final Collection columnNames, + final ColumnImpl columnPattern, final Object valuePattern) + { + return new Iterable>() { + public Iterator> iterator() { + return CursorImpl.this.columnMatchIterator( + columnNames, columnPattern, valuePattern); + } + }; + } + + public Iterator> columnMatchIterator( + Collection columnNames, Column columnPattern, + Object valuePattern) + { + return columnMatchIterator(columnNames, (ColumnImpl)columnPattern, + valuePattern); + } + + public Iterator> columnMatchIterator( + Collection columnNames, ColumnImpl columnPattern, + Object valuePattern) + { + return new ColumnMatchIterator(columnNames, columnPattern, valuePattern); + } + + public Iterable> rowMatchIterable( + Map rowPattern) + { + return rowMatchIterable(null, rowPattern); + } + + public Iterator> rowMatchIterator( + Map rowPattern) + { + return rowMatchIterator(null, rowPattern); + } + + public Iterable> rowMatchIterable( + final Collection columnNames, + final Map rowPattern) + { + return new Iterable>() { + public Iterator> iterator() { + return CursorImpl.this.rowMatchIterator( + columnNames, rowPattern); + } + }; + } + + public Iterator> rowMatchIterator( + Collection columnNames, Map 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 getNextRow() throws IOException { + return getNextRow(null); + } + + public Map getNextRow(Collection columnNames) + throws IOException + { + return getAnotherRow(columnNames, MOVE_FORWARD); + } + + public Map getPreviousRow() throws IOException { + return getPreviousRow(null); + } + + public Map getPreviousRow(Collection 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 getAnotherRow(Collection 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 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 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 rowPattern) + throws IOException + { + Map row = getCurrentRow(rowPattern.keySet()); + + if(rowPattern.size() != row.size()) { + return false; + } + + for(Map.Entry 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. + *

+ * 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. + *

+ * 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 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 getCurrentRow() throws IOException + { + return getCurrentRow(null); + } + + public Map getCurrentRow(Collection 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> + { + protected final Collection _columnNames; + protected Boolean _hasNext; + protected boolean _validRow; + + protected BaseIterator(Collection 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 next() { + if(!hasNext()) { + throw new NoSuchElementException(); + } + try { + Map 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 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 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 _rowPattern; + + private RowMatchIterator(Collection columnNames, + Map 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(); + } + } + +} diff --git a/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java b/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java index b040eb9..30f61db 100644 --- a/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java +++ b/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java @@ -1037,7 +1037,7 @@ public class DatabaseImpl implements Database List relationships = new ArrayList(); - 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> queryRowMap = new HashMap>(); for(Map 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 row : Cursor.createCursor(_queries)) { + for(Map row : CursorImpl.createCursor(_queries)) { Query.Row queryRow = new Query.Row(row); List 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 relationships) { for(Map 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 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 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 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 rowPat = new HashMap(); @@ -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; } diff --git a/src/java/com/healthmarketscience/jackcess/ExportUtil.java b/src/java/com/healthmarketscience/jackcess/ExportUtil.java index 1cb7eb7..5b37c70 100644 --- a/src/java/com/healthmarketscience/jackcess/ExportUtil.java +++ b/src/java/com/healthmarketscience/jackcess/ExportUtil.java @@ -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; } diff --git a/src/java/com/healthmarketscience/jackcess/FKEnforcer.java b/src/java/com/healthmarketscience/jackcess/FKEnforcer.java index b05a0f4..e6f1e5f 100644 --- a/src/java/com/healthmarketscience/jackcess/FKEnforcer.java +++ b/src/java/com/healthmarketscience/jackcess/FKEnforcer.java @@ -236,7 +236,7 @@ final class FKEnforcer Object[] newFromRow) throws IOException { - IndexCursor toCursor = joiner.getToCursor(); + IndexCursorImpl toCursor = joiner.getToCursor(); List fromCols = joiner.getColumns(); List toCols = joiner.getToIndex().getColumns(); Object[] toRow = new Object[joiner.getToTable().getColumnCount()]; diff --git a/src/java/com/healthmarketscience/jackcess/IndexCursor.java b/src/java/com/healthmarketscience/jackcess/IndexCursor.java index acd129f..b8b4556 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexCursor.java +++ b/src/java/com/healthmarketscience/jackcess/IndexCursor.java @@ -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 _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. - *

- * 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. - *

- * 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. - *

- * 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> entryIterator(Object... entryValues) - { - return entryIterator((Collection)null, entryValues); - } - + public Iterator> 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> entryIterator( - Collection columnNames, Object... entryValues) - { - return new EntryIterator(columnNames, toRowValues(entryValues)); - } - + Collection 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> entryIterable(Object... entryValues) - { - return entryIterable((Collection)null, entryValues); - } - + public Iterable> 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> entryIterable( - final Collection columnNames, final Object... entryValues) - { - return new Iterable>() { - public Iterator> 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 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 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 tmpRowPattern = new LinkedHashMap(); - 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(); - for(IndexData.ColumnDescriptor col : getIndex().getColumns()) { - _indexEntryPattern.add(col.getName()); - } - } - - // check the next row to see if it actually matches - Map 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 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 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 index 0000000..b425d07 --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/IndexCursorImpl.java @@ -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 _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. + *

+ * 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. + *

+ * 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. + *

+ * 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> entryIterator(Object... entryValues) + { + return entryIterator((Collection)null, entryValues); + } + + public Iterator> entryIterator( + Collection columnNames, Object... entryValues) + { + return new EntryIterator(columnNames, toRowValues(entryValues)); + } + + public Iterable> entryIterable(Object... entryValues) + { + return entryIterable((Collection)null, entryValues); + } + + public Iterable> entryIterable( + final Collection columnNames, final Object... entryValues) + { + return new Iterable>() { + public Iterator> 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 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 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 tmpRowPattern = new LinkedHashMap(); + 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(); + for(IndexData.ColumnDescriptor col : getIndex().getColumns()) { + _indexEntryPattern.add(col.getName()); + } + } + + // check the next row to see if it actually matches + Map 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 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)); + } + } + + +} diff --git a/src/java/com/healthmarketscience/jackcess/IndexData.java b/src/java/com/healthmarketscience/jackcess/IndexData.java index 965e7d8..b2be17d 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexData.java +++ b/src/java/com/healthmarketscience/jackcess/IndexData.java @@ -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(); } /** diff --git a/src/java/com/healthmarketscience/jackcess/Joiner.java b/src/java/com/healthmarketscience/jackcess/Joiner.java index cf25e29..81820dc 100644 --- a/src/java/com/healthmarketscience/jackcess/Joiner.java +++ b/src/java/com/healthmarketscience/jackcess/Joiner.java @@ -36,10 +36,10 @@ public class Joiner { private final IndexImpl _fromIndex; private final List _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; } diff --git a/src/java/com/healthmarketscience/jackcess/TableImpl.java b/src/java/com/healthmarketscience/jackcess/TableImpl.java index 9df4ce2..a4e21a4 100644 --- a/src/java/com/healthmarketscience/jackcess/TableImpl.java +++ b/src/java/com/healthmarketscience/jackcess/TableImpl.java @@ -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; } diff --git a/src/java/com/healthmarketscience/jackcess/UsageMap.java b/src/java/com/healthmarketscience/jackcess/UsageMap.java index 292c9bf..6e3b560 100644 --- a/src/java/com/healthmarketscience/jackcess/UsageMap.java +++ b/src/java/com/healthmarketscience/jackcess/UsageMap.java @@ -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); } /** diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java index 73a8894..af80531 100644 --- a/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java +++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java @@ -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 private final List _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 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( -- 2.39.5