- 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?
/*
-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
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
*
* @author James Ahlborn
*/
-public abstract class Cursor implements Iterable<Map<String, Object>>
-{
- private static final Log LOG = LogFactory.getLog(Cursor.class);
-
- /** boolean value indicating forward movement */
- public static final boolean MOVE_FORWARD = true;
- /** boolean value indicating reverse movement */
- public static final boolean MOVE_REVERSE = false;
-
- /** first position for the TableScanCursor */
- private static final ScanPosition FIRST_SCAN_POSITION =
- new ScanPosition(RowId.FIRST_ROW_ID);
- /** last position for the TableScanCursor */
- private static final ScanPosition LAST_SCAN_POSITION =
- new ScanPosition(RowId.LAST_ROW_ID);
-
- /** identifier for this cursor */
- private final Id _id;
- /** owning table */
- private final TableImpl _table;
- /** State used for reading the table rows */
- private final RowState _rowState;
- /** the first (exclusive) row id for this cursor */
- private final Position _firstPos;
- /** the last (exclusive) row id for this cursor */
- private final Position _lastPos;
- /** the previous row */
- protected Position _prevPos;
- /** the current row */
- protected Position _curPos;
- /** ColumnMatcher to be used when matching column values */
- protected ColumnMatcher _columnMatcher = SimpleColumnMatcher.INSTANCE;
-
- protected Cursor(Id id, TableImpl table, Position firstPos, Position lastPos) {
- _id = id;
- _table = table;
- _rowState = _table.createRowState();
- _firstPos = firstPos;
- _lastPos = lastPos;
- _curPos = firstPos;
- _prevPos = firstPos;
- }
-
- /**
- * Creates a normal, un-indexed cursor for the given table.
- * @param table the table over which this cursor will traverse
- */
- public static Cursor createCursor(TableImpl table) {
- return new TableScanCursor(table);
- }
-
- /**
- * Creates an indexed cursor for the given table.
- * <p>
- * Note, index based table traversal may not include all rows, as certain
- * types of indexes do not include all entries (namely, some indexes ignore
- * null entries, see {@link Index#shouldIgnoreNulls}).
- *
- * @param table the table over which this cursor will traverse
- * @param index index for the table which will define traversal order as
- * well as enhance certain lookups
- */
- public static Cursor createIndexCursor(TableImpl table, IndexImpl index)
- throws IOException
- {
- return IndexCursor.createCursor(table, index);
- }
-
- /**
- * Creates an indexed cursor for the given table, narrowed to the given
- * range.
- * <p>
- * Note, index based table traversal may not include all rows, as certain
- * types of indexes do not include all entries (namely, some indexes ignore
- * null entries, see {@link Index#shouldIgnoreNulls}).
- *
- * @param table the table over which this cursor will traverse
- * @param index index for the table which will define traversal order as
- * well as enhance certain lookups
- * @param startRow the first row of data for the cursor (inclusive), or
- * {@code null} for the first entry
- * @param endRow the last row of data for the cursor (inclusive), or
- * {@code null} for the last entry
- */
- public static Cursor createIndexCursor(TableImpl table, IndexImpl index,
- Object[] startRow, Object[] endRow)
- throws IOException
- {
- return IndexCursor.createCursor(table, index, startRow, endRow);
- }
-
- /**
- * Creates an indexed cursor for the given table, narrowed to the given
- * range.
- * <p>
- * Note, index based table traversal may not include all rows, as certain
- * types of indexes do not include all entries (namely, some indexes ignore
- * null entries, see {@link Index#shouldIgnoreNulls}).
- *
- * @param table the table over which this cursor will traverse
- * @param index index for the table which will define traversal order as
- * well as enhance certain lookups
- * @param startRow the first row of data for the cursor, or {@code null} for
- * the first entry
- * @param startInclusive whether or not startRow is inclusive or exclusive
- * @param endRow the last row of data for the cursor, or {@code null} for
- * the last entry
- * @param endInclusive whether or not endRow is inclusive or exclusive
- */
- public static Cursor createIndexCursor(TableImpl table, IndexImpl index,
- Object[] startRow,
- boolean startInclusive,
- Object[] endRow,
- boolean endInclusive)
- throws IOException
- {
- return IndexCursor.createCursor(table, index, startRow, startInclusive,
- endRow, endInclusive);
- }
-
- /**
- * Convenience method for finding a specific row in a table which matches a
- * given row "pattern". See {@link #findFirstRow(Map)} for details on the
- * rowPattern.
- * <p>
- * Warning, this method <i>always</i> starts searching from the beginning of
- * the Table (you cannot use it to find successive matches).
- *
- * @param table the table to search
- * @param rowPattern pattern to be used to find the row
- * @return the matching row or {@code null} if a match could not be found.
- */
- public static Map<String,Object> findRow(TableImpl table,
- Map<String,?> rowPattern)
- throws IOException
- {
- Cursor cursor = createCursor(table);
- if(cursor.findFirstRow(rowPattern)) {
- return cursor.getCurrentRow();
- }
- return null;
- }
-
- /**
- * Convenience method for finding a specific row in a table which matches a
- * given row "pattern". See {@link #findFirstRow(Column,Object)} for
- * details on the pattern.
- * <p>
- * Note, a {@code null} result value is ambiguous in that it could imply no
- * match or a matching row with {@code null} for the desired value. If
- * distinguishing this situation is important, you will need to use a Cursor
- * directly instead of this convenience method.
- *
- * @param table the table to search
- * @param column column whose value should be returned
- * @param columnPattern column being matched by the valuePattern
- * @param valuePattern value from the columnPattern which will match the
- * desired row
- * @return the matching row or {@code null} if a match could not be found.
- */
- public static Object findValue(TableImpl table, ColumnImpl column,
- ColumnImpl columnPattern, Object valuePattern)
- throws IOException
- {
- Cursor cursor = createCursor(table);
- if(cursor.findFirstRow(columnPattern, valuePattern)) {
- return cursor.getCurrentRowValue(column);
- }
- return null;
- }
-
- /**
- * Convenience method for finding a specific row in an indexed table which
- * matches a given row "pattern". See {@link #findFirstRow(Map)} for
- * details on the rowPattern.
- * <p>
- * Warning, this method <i>always</i> starts searching from the beginning of
- * the Table (you cannot use it to find successive matches).
- *
- * @param table the table to search
- * @param index index to assist the search
- * @param rowPattern pattern to be used to find the row
- * @return the matching row or {@code null} if a match could not be found.
- */
- public static Map<String,Object> findRow(TableImpl table, IndexImpl index,
- Map<String,?> rowPattern)
- throws IOException
- {
- Cursor cursor = createIndexCursor(table, index);
- if(cursor.findFirstRow(rowPattern)) {
- return cursor.getCurrentRow();
- }
- return null;
- }
-
- /**
- * Convenience method for finding a specific row in a table which matches a
- * given row "pattern". See {@link #findFirstRow(Column,Object)} for
- * details on the pattern.
- * <p>
- * Note, a {@code null} result value is ambiguous in that it could imply no
- * match or a matching row with {@code null} for the desired value. If
- * distinguishing this situation is important, you will need to use a Cursor
- * directly instead of this convenience method.
- *
- * @param table the table to search
- * @param index index to assist the search
- * @param column column whose value should be returned
- * @param columnPattern column being matched by the valuePattern
- * @param valuePattern value from the columnPattern which will match the
- * desired row
- * @return the matching row or {@code null} if a match could not be found.
- */
- public static Object findValue(TableImpl table, IndexImpl index, ColumnImpl column,
- ColumnImpl columnPattern, Object valuePattern)
- throws IOException
- {
- Cursor cursor = createIndexCursor(table, index);
- if(cursor.findFirstRow(columnPattern, valuePattern)) {
- return cursor.getCurrentRowValue(column);
- }
- return null;
- }
-
- public Id getId() {
- return _id;
- }
+public interface Cursor extends Iterable<Map<String, Object>>
+{
- public TableImpl getTable() {
- return _table;
- }
+ // FIXME, include iterable and iterator methods?
- public JetFormat getFormat() {
- return getTable().getFormat();
- }
+ public Id getId();
- public PageChannel getPageChannel() {
- return getTable().getPageChannel();
- }
+ public Table getTable();
/**
* Gets the currently configured ErrorHandler (always non-{@code null}).
* This will be used to handle all errors.
*/
- public ErrorHandler getErrorHandler() {
- return _rowState.getErrorHandler();
- }
+ public ErrorHandler getErrorHandler();
/**
* Sets a new ErrorHandler. If {@code null}, resets to using the
* ErrorHandler configured at the Table level.
*/
- public void setErrorHandler(ErrorHandler newErrorHandler) {
- _rowState.setErrorHandler(newErrorHandler);
- }
+ public void setErrorHandler(ErrorHandler newErrorHandler);
/**
* Returns the currently configured ColumnMatcher, always non-{@code null}.
*/
- public ColumnMatcher getColumnMatcher() {
- return _columnMatcher;
- }
+ public ColumnMatcher getColumnMatcher();
/**
* Sets a new ColumnMatcher. If {@code null}, resets to using the
* default matcher, {@link SimpleColumnMatcher#INSTANCE}.
*/
- public void setColumnMatcher(ColumnMatcher columnMatcher) {
- if(columnMatcher == null) {
- columnMatcher = getDefaultColumnMatcher();
- }
- _columnMatcher = columnMatcher;
- }
-
- /**
- * Returns the default ColumnMatcher for this Cursor.
- */
- protected ColumnMatcher getDefaultColumnMatcher() {
- return SimpleColumnMatcher.INSTANCE;
- }
+ public void setColumnMatcher(ColumnMatcher columnMatcher);
/**
* Returns the current state of the cursor which can be restored at a future
* 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
* cursorId equal to this cursor's id
*/
public void restoreSavepoint(Savepoint savepoint)
- throws IOException
- {
- if(!_id.equals(savepoint.getCursorId())) {
- throw new IllegalArgumentException(
- "Savepoint " + savepoint + " is not valid for this cursor with id "
- + _id);
- }
- restorePosition(savepoint.getCurrentPosition(),
- savepoint.getPreviousPosition());
- }
-
- /**
- * Returns the first row id (exclusive) as defined by this cursor.
- */
- protected Position getFirstPosition() {
- return _firstPos;
- }
-
- /**
- * Returns the last row id (exclusive) as defined by this cursor.
- */
- protected Position getLastPosition() {
- return _lastPos;
- }
+ throws IOException;
/**
* Resets this cursor for forward traversal. Calls {@link #beforeFirst}.
*/
- public void reset() {
- beforeFirst();
- }
+ public void reset();
/**
* Resets this cursor for forward traversal (sets cursor to before the first
* row).
*/
- public void beforeFirst() {
- reset(MOVE_FORWARD);
- }
-
+ public void beforeFirst();
+
/**
* Resets this cursor for reverse traversal (sets cursor to after the last
* row).
*/
- public void afterLast() {
- reset(MOVE_REVERSE);
- }
+ public void afterLast();
/**
* Returns {@code true} if the cursor is currently positioned before the
* first row, {@code false} otherwise.
*/
- public boolean isBeforeFirst()
- throws IOException
- {
- if(getFirstPosition().equals(_curPos)) {
- return !recheckPosition(MOVE_REVERSE);
- }
- return false;
- }
-
+ public boolean isBeforeFirst() throws IOException;
+
/**
* Returns {@code true} if the cursor is currently positioned after the
* last row, {@code false} otherwise.
*/
- public boolean isAfterLast()
- throws IOException
- {
- if(getLastPosition().equals(_curPos)) {
- return !recheckPosition(MOVE_FORWARD);
- }
- return false;
- }
+ public boolean isAfterLast() throws IOException;
/**
* Returns {@code true} if the row at which the cursor is currently
* positioned is deleted, {@code false} otherwise (including invalid rows).
*/
- public boolean isCurrentRowDeleted()
- throws IOException
- {
- // we need to ensure that the "deleted" flag has been read for this row
- // (or re-read if the table has been recently modified)
- TableImpl.positionAtRowData(_rowState, _curPos.getRowId());
- return _rowState.isDeleted();
- }
-
- /**
- * Resets this cursor for traversing the given direction.
- */
- protected void reset(boolean moveForward) {
- _curPos = getDirHandler(moveForward).getBeginningPosition();
- _prevPos = _curPos;
- _rowState.reset();
- }
+ public boolean isCurrentRowDeleted() throws IOException;
/**
* Returns an Iterable whose iterator() method calls <code>afterLast</code>
* @throws IllegalStateException if an IOException is thrown by one of the
* operations, the actual exception will be contained within
*/
- public Iterable<Map<String, Object>> reverseIterable() {
- return reverseIterable(null);
- }
+ public Iterable<Map<String, Object>> reverseIterable();
/**
* Returns an Iterable whose iterator() method calls <code>afterLast</code>
* operations, the actual exception will be contained within
*/
public Iterable<Map<String, Object>> reverseIterable(
- final Collection<String> columnNames)
- {
- return new Iterable<Map<String, Object>>() {
- public Iterator<Map<String, Object>> iterator() {
- return new RowIterator(columnNames, MOVE_REVERSE);
- }
- };
- }
-
+ Collection<String> columnNames);
+
/**
* Calls <code>beforeFirst</code> on this cursor and returns a modifiable
* Iterator which will iterate through all the rows of this table. Use of
* @throws IllegalStateException if an IOException is thrown by one of the
* operations, the actual exception will be contained within
*/
- public Iterator<Map<String, Object>> iterator()
- {
- return iterator(null);
- }
-
+ public Iterator<Map<String, Object>> iterator();
+
/**
* Returns an Iterable whose iterator() method returns the result of a call
* to {@link #iterator(Collection)}
* operations, the actual exception will be contained within
*/
public Iterable<Map<String, Object>> iterable(
- final Collection<String> columnNames)
- {
- return new Iterable<Map<String, Object>>() {
- public Iterator<Map<String, Object>> iterator() {
- return Cursor.this.iterator(columnNames);
- }
- };
- }
-
+ Collection<String> columnNames);
+
/**
* Calls <code>beforeFirst</code> on this table and returns a modifiable
* Iterator which will iterate through all the rows of this table, returning
* @throws IllegalStateException if an IOException is thrown by one of the
* operations, the actual exception will be contained within
*/
- public Iterator<Map<String, Object>> iterator(Collection<String> columnNames)
- {
- return new RowIterator(columnNames, MOVE_FORWARD);
- }
+ public Iterator<Map<String, Object>> iterator(Collection<String> columnNames);
/**
* Returns an Iterable whose iterator() method returns the result of a call
* operations, the actual exception will be contained within
*/
public Iterable<Map<String, Object>> columnMatchIterable(
- ColumnImpl columnPattern, Object valuePattern)
- {
- return columnMatchIterable(null, columnPattern, valuePattern);
- }
-
+ Column columnPattern, Object valuePattern);
+
/**
* Calls <code>beforeFirst</code> on this cursor and returns a modifiable
* Iterator which will iterate through all the rows of this table which
* operations, the actual exception will be contained within
*/
public Iterator<Map<String, Object>> columnMatchIterator(
- ColumnImpl columnPattern, Object valuePattern)
- {
- return columnMatchIterator(null, columnPattern, valuePattern);
- }
-
- /**
- * Returns an Iterable whose iterator() method returns the result of a call
- * to {@link #columnMatchIterator(Collection,Column,Object)}
- * @throws IllegalStateException if an IOException is thrown by one of the
- * operations, the actual exception will be contained within
- */
- public Iterable<Map<String, Object>> columnMatchIterable(
- final Collection<String> columnNames,
- final ColumnImpl columnPattern, final Object valuePattern)
- {
- return new Iterable<Map<String, Object>>() {
- public Iterator<Map<String, Object>> iterator() {
- return Cursor.this.columnMatchIterator(
- columnNames, columnPattern, valuePattern);
- }
- };
- }
-
+ Column columnPattern, Object valuePattern);
+
/**
* Calls <code>beforeFirst</code> on this table and returns a modifiable
* Iterator which will iterate through all the rows of this table which
* operations, the actual exception will be contained within
*/
public Iterator<Map<String, Object>> columnMatchIterator(
- Collection<String> columnNames, ColumnImpl columnPattern, Object valuePattern)
- {
- return new ColumnMatchIterator(columnNames, columnPattern, valuePattern);
- }
+ Collection<String> columnNames, Column columnPattern, Object valuePattern);
/**
* Returns an Iterable whose iterator() method returns the result of a call
* operations, the actual exception will be contained within
*/
public Iterable<Map<String, Object>> rowMatchIterable(
- Map<String,?> rowPattern)
- {
- return rowMatchIterable(null, rowPattern);
- }
-
+ Map<String,?> rowPattern);
+
/**
* Calls <code>beforeFirst</code> on this cursor and returns a modifiable
* Iterator which will iterate through all the rows of this table which
* operations, the actual exception will be contained within
*/
public Iterator<Map<String, Object>> rowMatchIterator(
- Map<String,?> rowPattern)
- {
- return rowMatchIterator(null, rowPattern);
- }
-
+ Map<String,?> rowPattern);
+
/**
* Returns an Iterable whose iterator() method returns the result of a call
* to {@link #rowMatchIterator(Collection,Map)}
* operations, the actual exception will be contained within
*/
public Iterable<Map<String, Object>> rowMatchIterable(
- final Collection<String> columnNames,
- final Map<String,?> rowPattern)
- {
- return new Iterable<Map<String, Object>>() {
- public Iterator<Map<String, Object>> iterator() {
- return Cursor.this.rowMatchIterator(
- columnNames, rowPattern);
- }
- };
- }
-
+ Collection<String> columnNames,
+ Map<String,?> rowPattern);
+
/**
* Calls <code>beforeFirst</code> on this table and returns a modifiable
* Iterator which will iterate through all the rows of this table which
* operations, the actual exception will be contained within
*/
public Iterator<Map<String, Object>> rowMatchIterator(
- Collection<String> columnNames, Map<String,?> rowPattern)
- {
- return new RowMatchIterator(columnNames, rowPattern);
- }
+ Collection<String> columnNames, Map<String,?> rowPattern);
/**
* Delete the current row.
* @throws IllegalStateException if the current row is not valid (at
* beginning or end of table), or already deleted.
*/
- public void deleteCurrentRow() throws IOException {
- _table.deleteRow(_rowState, _curPos.getRowId());
- }
+ public void deleteCurrentRow() throws IOException;
/**
* Update the current row.
* @throws IllegalStateException if the current row is not valid (at
* beginning or end of table), or deleted.
*/
- public void updateCurrentRow(Object... row) throws IOException {
- _table.updateRow(_rowState, _curPos.getRowId(), row);
- }
+ public void updateCurrentRow(Object... row) throws IOException;
/**
* Moves to the next row in the table and returns it.
* @return The next row in this table (Column name -> Column value), or
* {@code null} if no next row is found
*/
- public Map<String, Object> getNextRow() throws IOException {
- return getNextRow(null);
- }
+ public Map<String, Object> getNextRow() throws IOException;
/**
* Moves to the next row in the table and returns it.
* {@code null} if no next row is found
*/
public Map<String, Object> getNextRow(Collection<String> columnNames)
- throws IOException
- {
- return getAnotherRow(columnNames, MOVE_FORWARD);
- }
+ throws IOException;
/**
* Moves to the previous row in the table and returns it.
* @return The previous row in this table (Column name -> Column value), or
* {@code null} if no previous row is found
*/
- public Map<String, Object> getPreviousRow() throws IOException {
- return getPreviousRow(null);
- }
+ public Map<String, Object> getPreviousRow() throws IOException;
/**
* Moves to the previous row in the table and returns it.
* {@code null} if no previous row is found
*/
public Map<String, Object> getPreviousRow(Collection<String> columnNames)
- throws IOException
- {
- return getAnotherRow(columnNames, MOVE_REVERSE);
- }
-
-
- /**
- * Moves to another row in the table based on the given direction and
- * returns it.
- * @param columnNames Only column names in this collection will be returned
- * @return another row in this table (Column name -> Column value), where
- * "next" may be backwards if moveForward is {@code false}, or
- * {@code null} if there is not another row in the given direction.
- */
- private Map<String, Object> getAnotherRow(Collection<String> columnNames,
- boolean moveForward)
- throws IOException
- {
- if(moveToAnotherRow(moveForward)) {
- return getCurrentRow(columnNames);
- }
- return null;
- }
+ throws IOException;
/**
* Moves to the next row as defined by this cursor.
* @return {@code true} if a valid next row was found, {@code false}
* otherwise
*/
- public boolean moveToNextRow()
- throws IOException
- {
- return moveToAnotherRow(MOVE_FORWARD);
- }
+ public boolean moveToNextRow() throws IOException;
/**
* Moves to the previous row as defined by this cursor.
* @return {@code true} if a valid previous row was found, {@code false}
* otherwise
*/
- public boolean moveToPreviousRow()
- throws IOException
- {
- return moveToAnotherRow(MOVE_REVERSE);
- }
-
- /**
- * Moves to another row in the given direction as defined by this cursor.
- * @return {@code true} if another valid row was found in the given
- * direction, {@code false} otherwise
- */
- private boolean moveToAnotherRow(boolean moveForward)
- throws IOException
- {
- if(_curPos.equals(getDirHandler(moveForward).getEndPosition())) {
- // already at end, make sure nothing has changed
- return recheckPosition(moveForward);
- }
-
- return moveToAnotherRowImpl(moveForward);
- }
-
- /**
- * Restores a current position for the cursor (current position becomes
- * previous position).
- */
- protected void restorePosition(Position curPos)
- throws IOException
- {
- restorePosition(curPos, _curPos);
- }
-
- /**
- * Restores a current and previous position for the cursor if the given
- * positions are different from the current positions.
- */
- protected final void restorePosition(Position curPos, Position prevPos)
- throws IOException
- {
- if(!curPos.equals(_curPos) || !prevPos.equals(_prevPos)) {
- restorePositionImpl(curPos, prevPos);
- }
- }
-
- /**
- * Restores a current and previous position for the cursor.
- */
- protected void restorePositionImpl(Position curPos, Position prevPos)
- throws IOException
- {
- // make the current position previous, and the new position current
- _prevPos = _curPos;
- _curPos = curPos;
- _rowState.reset();
- }
-
- /**
- * Rechecks the current position if the underlying data structures have been
- * modified.
- * @return {@code true} if the cursor ended up in a new position,
- * {@code false} otherwise.
- */
- private boolean recheckPosition(boolean moveForward)
- throws IOException
- {
- if(isUpToDate()) {
- // nothing has changed
- return false;
- }
-
- // move the cursor back to the previous position
- restorePosition(_prevPos);
- return moveToAnotherRowImpl(moveForward);
- }
-
- /**
- * Does the grunt work of moving the cursor to another position in the given
- * direction.
- */
- private boolean moveToAnotherRowImpl(boolean moveForward)
- throws IOException
- {
- _rowState.reset();
- _prevPos = _curPos;
- _curPos = findAnotherPosition(_rowState, _curPos, moveForward);
- TableImpl.positionAtRowHeader(_rowState, _curPos.getRowId());
- return(!_curPos.equals(getDirHandler(moveForward).getEndPosition()));
- }
-
- /**
- * @deprecated renamed to {@link #findFirstRow(Column,Object)} to be more clear
- */
- @Deprecated
- public boolean findRow(ColumnImpl columnPattern, Object valuePattern)
- throws IOException
- {
- return findFirstRow(columnPattern, valuePattern);
- }
+ public boolean moveToPreviousRow() throws IOException;
/**
* Moves to the first row (as defined by the cursor) where the given column
* @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
* @return {@code true} if a valid row was found with the given value,
* {@code false} if no row was found
*/
- public boolean findNextRow(ColumnImpl columnPattern, Object valuePattern)
- throws IOException
- {
- Position curPos = _curPos;
- Position prevPos = _prevPos;
- boolean found = false;
- try {
- found = findNextRowImpl(columnPattern, valuePattern);
- return found;
- } finally {
- if(!found) {
- try {
- restorePosition(curPos, prevPos);
- } catch(IOException e) {
- LOG.error("Failed restoring position", e);
- }
- }
- }
- }
-
- /**
- * @deprecated renamed to {@link #findFirstRow(Map)} to be more clear
- */
- @Deprecated
- public boolean findRow(Map<String,?> rowPattern)
- throws IOException
- {
- return findFirstRow(rowPattern);
- }
+ public boolean findNextRow(Column columnPattern, Object valuePattern)
+ throws IOException;
/**
* Moves to the first row (as defined by the cursor) where the given columns
* @return {@code true} if a valid row was found with the given values,
* {@code false} if no row was found
*/
- public boolean findFirstRow(Map<String,?> rowPattern)
- throws IOException
- {
- Position curPos = _curPos;
- Position prevPos = _prevPos;
- boolean found = false;
- try {
- beforeFirst();
- found = findNextRowImpl(rowPattern);
- return found;
- } finally {
- if(!found) {
- try {
- restorePosition(curPos, prevPos);
- } catch(IOException e) {
- LOG.error("Failed restoring position", e);
- }
- }
- }
- }
+ public boolean findFirstRow(Map<String,?> rowPattern) throws IOException;
/**
* Moves to the next row (as defined by the cursor) where the given columns
* @return {@code true} if a valid row was found with the given values,
* {@code false} if no row was found
*/
- public boolean findNextRow(Map<String,?> rowPattern)
- throws IOException
- {
- Position curPos = _curPos;
- Position prevPos = _prevPos;
- boolean found = false;
- try {
- found = findNextRowImpl(rowPattern);
- return found;
- } finally {
- if(!found) {
- try {
- restorePosition(curPos, prevPos);
- } catch(IOException e) {
- LOG.error("Failed restoring position", e);
- }
- }
- }
- }
+ public boolean findNextRow(Map<String,?> rowPattern) throws IOException;
/**
* Returns {@code true} if the current row matches the given pattern.
* @param valuePattern value which is tested for equality with the
* corresponding value in the current row
*/
- public boolean currentRowMatches(ColumnImpl columnPattern, Object valuePattern)
- throws IOException
- {
- return _columnMatcher.matches(getTable(), columnPattern.getName(),
- valuePattern,
- getCurrentRowValue(columnPattern));
- }
-
+ public boolean currentRowMatches(Column columnPattern, Object valuePattern)
+ throws IOException;
+
/**
* Returns {@code true} if the current row matches the given pattern.
* @param rowPattern column names and values which must be equal to the
* corresponding values in the current row
*/
- public boolean currentRowMatches(Map<String,?> rowPattern)
- throws IOException
- {
- Map<String,Object> row = getCurrentRow(rowPattern.keySet());
-
- if(rowPattern.size() != row.size()) {
- return false;
- }
-
- for(Map.Entry<String,Object> e : row.entrySet()) {
- String columnName = e.getKey();
- if(!_columnMatcher.matches(getTable(), columnName,
- rowPattern.get(columnName), e.getValue())) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Moves to the next row (as defined by the cursor) where the given column
- * has the given value. Caller manages save/restore on failure.
- * <p>
- * Default implementation scans the table from beginning to end.
- *
- * @param columnPattern column from the table for this cursor which is being
- * matched by the valuePattern
- * @param valuePattern value which is equal to the corresponding value in
- * the matched row
- * @return {@code true} if a valid row was found with the given value,
- * {@code false} if no row was found
- */
- protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
- throws IOException
- {
- while(moveToNextRow()) {
- if(currentRowMatches(columnPattern, valuePattern)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Moves to the next row (as defined by the cursor) where the given columns
- * have the given values. Caller manages save/restore on failure.
- * <p>
- * Default implementation scans the table from beginning to end.
- *
- * @param rowPattern column names and values which must be equal to the
- * corresponding values in the matched row
- * @return {@code true} if a valid row was found with the given values,
- * {@code false} if no row was found
- */
- protected boolean findNextRowImpl(Map<String,?> rowPattern)
- throws IOException
- {
- while(moveToNextRow()) {
- if(currentRowMatches(rowPattern)) {
- return true;
- }
- }
- return false;
- }
+ public boolean currentRowMatches(Map<String,?> rowPattern) throws IOException;
/**
* Moves forward as many rows as possible up to the given number of rows.
* @return the number of rows moved.
*/
- public int moveNextRows(int numRows)
- throws IOException
- {
- return moveSomeRows(numRows, MOVE_FORWARD);
- }
+ public int moveNextRows(int numRows) throws IOException;
/**
* Moves backward as many rows as possible up to the given number of rows.
* @return the number of rows moved.
*/
- public int movePreviousRows(int numRows)
- throws IOException
- {
- return moveSomeRows(numRows, MOVE_REVERSE);
- }
-
- /**
- * Moves as many rows as possible in the given direction up to the given
- * number of rows.
- * @return the number of rows moved.
- */
- private int moveSomeRows(int numRows, boolean moveForward)
- throws IOException
- {
- int numMovedRows = 0;
- while((numMovedRows < numRows) && moveToAnotherRow(moveForward)) {
- ++numMovedRows;
- }
- return numMovedRows;
- }
+ public int movePreviousRows(int numRows) throws IOException;
/**
* Returns the current row in this cursor (Column name -> Column value).
*/
- public Map<String, Object> getCurrentRow()
- throws IOException
- {
- return getCurrentRow(null);
- }
+ public Map<String, Object> getCurrentRow() throws IOException;
/**
* Returns the current row in this cursor (Column name -> Column value).
* @param columnNames Only column names in this collection will be returned
*/
public Map<String, Object> getCurrentRow(Collection<String> columnNames)
- throws IOException
- {
- return _table.getRow(_rowState, _curPos.getRowId(), columnNames);
- }
+ throws IOException;
/**
* Returns the given column from the current row.
*/
- public Object getCurrentRowValue(ColumnImpl column)
- throws IOException
- {
- return _table.getRowValue(_rowState, _curPos.getRowId(), column);
- }
+ public Object getCurrentRowValue(Column column) throws IOException;
/**
* Updates a single value in the current row.
* @throws IllegalStateException if the current row is not valid (at
* beginning or end of table), or deleted.
*/
- public void setCurrentRowValue(ColumnImpl column, Object value)
- throws IOException
- {
- Object[] row = new Object[_table.getColumnCount()];
- Arrays.fill(row, Column.KEEP_VALUE);
- column.setRowValue(row, value);
- _table.updateRow(_rowState, _curPos.getRowId(), row);
- }
-
- /**
- * Returns {@code true} if this cursor is up-to-date with respect to the
- * relevant table and related table objects, {@code false} otherwise.
- */
- protected boolean isUpToDate() {
- return _rowState.isUpToDate();
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + " CurPosition " + _curPos +
- ", PrevPosition " + _prevPos;
- }
-
- /**
- * Finds the next non-deleted row after the given row (as defined by this
- * cursor) and returns the id of the row, where "next" may be backwards if
- * moveForward is {@code false}. If there are no more rows, the returned
- * rowId should equal the value returned by {@link #getLastPosition} if
- * moving forward and {@link #getFirstPosition} if moving backward.
- */
- protected abstract Position findAnotherPosition(RowState rowState,
- Position curPos,
- boolean moveForward)
+ public void setCurrentRowValue(Column column, Object value)
throws IOException;
- /**
- * Returns the DirHandler for the given movement direction.
- */
- protected abstract DirHandler getDirHandler(boolean moveForward);
-
-
- /**
- * Base implementation of iterator for this cursor, modifiable.
- */
- protected abstract class BaseIterator
- implements Iterator<Map<String, Object>>
- {
- protected final Collection<String> _columnNames;
- protected Boolean _hasNext;
- protected boolean _validRow;
-
- protected BaseIterator(Collection<String> columnNames)
- {
- _columnNames = columnNames;
- }
-
- public boolean hasNext() {
- if(_hasNext == null) {
- try {
- _hasNext = findNext();
- _validRow = _hasNext;
- } catch(IOException e) {
- throw new IllegalStateException(e);
- }
- }
- return _hasNext;
- }
-
- public Map<String, Object> next() {
- if(!hasNext()) {
- throw new NoSuchElementException();
- }
- try {
- Map<String, Object> rtn = getCurrentRow(_columnNames);
- _hasNext = null;
- return rtn;
- } catch(IOException e) {
- throw new IllegalStateException(e);
- }
- }
-
- public void remove() {
- if(_validRow) {
- try {
- deleteCurrentRow();
- _validRow = false;
- } catch(IOException e) {
- throw new IllegalStateException(e);
- }
- } else {
- throw new IllegalStateException("Not at valid row");
- }
- }
-
- protected abstract boolean findNext() throws IOException;
- }
-
-
- /**
- * Row iterator for this cursor, modifiable.
- */
- private final class RowIterator extends BaseIterator
- {
- private final boolean _moveForward;
-
- private RowIterator(Collection<String> columnNames, boolean moveForward)
- {
- super(columnNames);
- _moveForward = moveForward;
- reset(_moveForward);
- }
-
- @Override
- protected boolean findNext() throws IOException {
- return moveToAnotherRow(_moveForward);
- }
- }
-
-
- /**
- * Row iterator for this cursor, modifiable.
- */
- private final class ColumnMatchIterator extends BaseIterator
- {
- private final ColumnImpl _columnPattern;
- private final Object _valuePattern;
-
- private ColumnMatchIterator(Collection<String> columnNames,
- ColumnImpl columnPattern, Object valuePattern)
- {
- super(columnNames);
- _columnPattern = columnPattern;
- _valuePattern = valuePattern;
- beforeFirst();
- }
-
- @Override
- protected boolean findNext() throws IOException {
- return findNextRow(_columnPattern, _valuePattern);
- }
- }
-
-
- /**
- * Row iterator for this cursor, modifiable.
- */
- private final class RowMatchIterator extends BaseIterator
- {
- private final Map<String,?> _rowPattern;
-
- private RowMatchIterator(Collection<String> columnNames,
- Map<String,?> rowPattern)
- {
- super(columnNames);
- _rowPattern = rowPattern;
- beforeFirst();
- }
-
- @Override
- protected boolean findNext() throws IOException {
- return findNextRow(_rowPattern);
- }
- }
-
-
- /**
- * Handles moving the cursor in a given direction. Separates cursor
- * logic from value storage.
- */
- protected abstract class DirHandler
- {
- public abstract Position getBeginningPosition();
- public abstract Position getEndPosition();
- }
-
-
- /**
- * Simple un-indexed cursor.
- */
- private static final class TableScanCursor extends Cursor
- {
- /** ScanDirHandler for forward traversal */
- private final ScanDirHandler _forwardDirHandler =
- new ForwardScanDirHandler();
- /** ScanDirHandler for backward traversal */
- private final ScanDirHandler _reverseDirHandler =
- new ReverseScanDirHandler();
- /** Cursor over the pages that this table owns */
- private final UsageMap.PageCursor _ownedPagesCursor;
-
- private TableScanCursor(TableImpl table) {
- super(new Id(table, null), table,
- FIRST_SCAN_POSITION, LAST_SCAN_POSITION);
- _ownedPagesCursor = table.getOwnedPagesCursor();
- }
-
- @Override
- protected ScanDirHandler getDirHandler(boolean moveForward) {
- return (moveForward ? _forwardDirHandler : _reverseDirHandler);
- }
-
- @Override
- protected boolean isUpToDate() {
- return(super.isUpToDate() && _ownedPagesCursor.isUpToDate());
- }
-
- @Override
- protected void reset(boolean moveForward) {
- _ownedPagesCursor.reset(moveForward);
- super.reset(moveForward);
- }
-
- @Override
- protected void restorePositionImpl(Position curPos, Position prevPos)
- throws IOException
- {
- if(!(curPos instanceof ScanPosition) ||
- !(prevPos instanceof ScanPosition)) {
- throw new IllegalArgumentException(
- "Restored positions must be scan positions");
- }
- _ownedPagesCursor.restorePosition(curPos.getRowId().getPageNumber(),
- prevPos.getRowId().getPageNumber());
- super.restorePositionImpl(curPos, prevPos);
- }
-
- @Override
- protected Position findAnotherPosition(RowState rowState, Position curPos,
- boolean moveForward)
- throws IOException
- {
- ScanDirHandler handler = getDirHandler(moveForward);
-
- // figure out how many rows are left on this page so we can find the
- // next row
- RowId curRowId = curPos.getRowId();
- TableImpl.positionAtRowHeader(rowState, curRowId);
- int currentRowNumber = curRowId.getRowNumber();
-
- // loop until we find the next valid row or run out of pages
- while(true) {
-
- currentRowNumber = handler.getAnotherRowNumber(currentRowNumber);
- curRowId = new RowId(curRowId.getPageNumber(), currentRowNumber);
- TableImpl.positionAtRowHeader(rowState, curRowId);
-
- if(!rowState.isValid()) {
-
- // load next page
- curRowId = new RowId(handler.getAnotherPageNumber(),
- RowId.INVALID_ROW_NUMBER);
- TableImpl.positionAtRowHeader(rowState, curRowId);
-
- if(!rowState.isHeaderPageNumberValid()) {
- //No more owned pages. No more rows.
- return handler.getEndPosition();
- }
-
- // update row count and initial row number
- currentRowNumber = handler.getInitialRowNumber(
- rowState.getRowsOnHeaderPage());
-
- } else if(!rowState.isDeleted()) {
-
- // we found a valid, non-deleted row, return it
- return new ScanPosition(curRowId);
- }
-
- }
- }
-
- /**
- * Handles moving the table scan cursor in a given direction. Separates
- * cursor logic from value storage.
- */
- private abstract class ScanDirHandler extends DirHandler {
- public abstract int getAnotherRowNumber(int curRowNumber);
- public abstract int getAnotherPageNumber();
- public abstract int getInitialRowNumber(int rowsOnPage);
- }
-
- /**
- * Handles moving the table scan cursor forward.
- */
- private final class ForwardScanDirHandler extends ScanDirHandler {
- @Override
- public Position getBeginningPosition() {
- return getFirstPosition();
- }
- @Override
- public Position getEndPosition() {
- return getLastPosition();
- }
- @Override
- public int getAnotherRowNumber(int curRowNumber) {
- return curRowNumber + 1;
- }
- @Override
- public int getAnotherPageNumber() {
- return _ownedPagesCursor.getNextPage();
- }
- @Override
- public int getInitialRowNumber(int rowsOnPage) {
- return -1;
- }
- }
-
- /**
- * Handles moving the table scan cursor backward.
- */
- private final class ReverseScanDirHandler extends ScanDirHandler {
- @Override
- public Position getBeginningPosition() {
- return getLastPosition();
- }
- @Override
- public Position getEndPosition() {
- return getFirstPosition();
- }
- @Override
- public int getAnotherRowNumber(int curRowNumber) {
- return curRowNumber - 1;
- }
- @Override
- public int getAnotherPageNumber() {
- return _ownedPagesCursor.getPreviousPage();
- }
- @Override
- public int getInitialRowNumber(int rowsOnPage) {
- return rowsOnPage;
- }
- }
-
- }
-
-
/**
* Identifier for a cursor. Will be equal to any other cursor of the same
* type for the same table. Primarily used to check the validity of a
* Savepoint.
*/
- public static final class Id
- {
- private final String _tableName;
- private final String _indexName;
-
- protected Id(TableImpl table, Index index) {
- _tableName = table.getName();
- _indexName = ((index != null) ? index.getName() : null);
- }
-
- @Override
- public int hashCode() {
- return _tableName.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- return((this == o) ||
- ((o != null) && (getClass() == o.getClass()) &&
- ObjectUtils.equals(_tableName, ((Id)o)._tableName) &&
- ObjectUtils.equals(_indexName, ((Id)o)._indexName)));
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + " " + _tableName + ":" + _indexName;
- }
+ public interface Id
+ {
}
- /**
- * Value object which represents a complete save state of the cursor.
- */
- public static final class Savepoint
- {
- private final Id _cursorId;
- private final Position _curPos;
- private final Position _prevPos;
-
- private Savepoint(Id cursorId, Position curPos, Position prevPos) {
- _cursorId = cursorId;
- _curPos = curPos;
- _prevPos = prevPos;
- }
-
- public Id getCursorId() {
- return _cursorId;
- }
-
- public Position getCurrentPosition() {
- return _curPos;
- }
-
- private Position getPreviousPosition() {
- return _prevPos;
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + " " + _cursorId + " CurPosition " +
- _curPos + ", PrevPosition " + _prevPos;
- }
- }
-
/**
* Value object which maintains the current position of the cursor.
*/
- public static abstract class Position
- {
- protected Position() {
- }
-
- @Override
- public final int hashCode() {
- return getRowId().hashCode();
- }
-
- @Override
- public final boolean equals(Object o) {
- return((this == o) ||
- ((o != null) && (getClass() == o.getClass()) && equalsImpl(o)));
- }
-
+ public interface Position
+ {
/**
* Returns the unique RowId of the position of the cursor.
*/
- public abstract RowId getRowId();
-
- /**
- * Returns {@code true} if the subclass specific info in a Position is
- * equal, {@code false} otherwise.
- * @param o object being tested for equality, guaranteed to be the same
- * class as this object
- */
- protected abstract boolean equalsImpl(Object o);
+ public RowId getRowId();
}
/**
- * Value object which maintains the current position of a TableScanCursor.
+ * Value object which represents a complete save state of the cursor.
+ * Savepoints are created by calling {@link Cursor#getSavepoint} and used by
+ * calling {@link Cursor#restoreSavepoint} to return the the cursor state at
+ * the time the Savepoint was created.
*/
- private static final class ScanPosition extends Position
+ public interface Savepoint
{
- private final RowId _rowId;
-
- private ScanPosition(RowId rowId) {
- _rowId = rowId;
- }
-
- @Override
- public RowId getRowId() {
- return _rowId;
- }
+ public Id getCursorId();
- @Override
- protected boolean equalsImpl(Object o) {
- return getRowId().equals(((ScanPosition)o).getRowId());
- }
-
- @Override
- public String toString() {
- return "RowId = " + getRowId();
- }
+ public Position getCurrentPosition();
}
-
+
}
/** 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;
/**
* 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;
}
* 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);
}
* 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();
}
}
--- /dev/null
+/*
+Copyright (c) 2007 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import com.healthmarketscience.jackcess.TableImpl.RowState;
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * Manages iteration for a Table. Different cursors provide different methods
+ * of traversing a table. Cursors should be fairly robust in the face of
+ * table modification during traversal (although depending on how the table is
+ * traversed, row updates may or may not be seen). Multiple cursors may
+ * traverse the same table simultaneously.
+ * <p>
+ * The Cursor provides a variety of static utility methods to construct
+ * cursors with given characteristics or easily search for specific values.
+ * For even friendlier and more flexible construction, see
+ * {@link CursorBuilder}.
+ * <p>
+ * Is not thread-safe.
+ *
+ * @author James Ahlborn
+ */
+public abstract class CursorImpl implements Cursor
+{
+ private static final Log LOG = LogFactory.getLog(CursorImpl.class);
+
+ /** boolean value indicating forward movement */
+ public static final boolean MOVE_FORWARD = true;
+ /** boolean value indicating reverse movement */
+ public static final boolean MOVE_REVERSE = false;
+
+ /** first position for the TableScanCursor */
+ private static final ScanPosition FIRST_SCAN_POSITION =
+ new ScanPosition(RowId.FIRST_ROW_ID);
+ /** last position for the TableScanCursor */
+ private static final ScanPosition LAST_SCAN_POSITION =
+ new ScanPosition(RowId.LAST_ROW_ID);
+
+ /** identifier for this cursor */
+ private final IdImpl _id;
+ /** owning table */
+ private final TableImpl _table;
+ /** State used for reading the table rows */
+ private final RowState _rowState;
+ /** the first (exclusive) row id for this cursor */
+ private final PositionImpl _firstPos;
+ /** the last (exclusive) row id for this cursor */
+ private final PositionImpl _lastPos;
+ /** the previous row */
+ protected PositionImpl _prevPos;
+ /** the current row */
+ protected PositionImpl _curPos;
+ /** ColumnMatcher to be used when matching column values */
+ protected ColumnMatcher _columnMatcher = SimpleColumnMatcher.INSTANCE;
+
+ protected CursorImpl(IdImpl id, TableImpl table, PositionImpl firstPos,
+ PositionImpl lastPos) {
+ _id = id;
+ _table = table;
+ _rowState = _table.createRowState();
+ _firstPos = firstPos;
+ _lastPos = lastPos;
+ _curPos = firstPos;
+ _prevPos = firstPos;
+ }
+
+ /**
+ * Creates a normal, un-indexed cursor for the given table.
+ * @param table the table over which this cursor will traverse
+ */
+ public static CursorImpl createCursor(TableImpl table) {
+ return new TableScanCursor(table);
+ }
+
+ /**
+ * Creates an indexed cursor for the given table.
+ * <p>
+ * Note, index based table traversal may not include all rows, as certain
+ * types of indexes do not include all entries (namely, some indexes ignore
+ * null entries, see {@link Index#shouldIgnoreNulls}).
+ *
+ * @param table the table over which this cursor will traverse
+ * @param index index for the table which will define traversal order as
+ * well as enhance certain lookups
+ */
+ public static CursorImpl createIndexCursor(TableImpl table, IndexImpl index)
+ throws IOException
+ {
+ return IndexCursorImpl.createCursor(table, index);
+ }
+
+ /**
+ * Creates an indexed cursor for the given table, narrowed to the given
+ * range.
+ * <p>
+ * Note, index based table traversal may not include all rows, as certain
+ * types of indexes do not include all entries (namely, some indexes ignore
+ * null entries, see {@link Index#shouldIgnoreNulls}).
+ *
+ * @param table the table over which this cursor will traverse
+ * @param index index for the table which will define traversal order as
+ * well as enhance certain lookups
+ * @param startRow the first row of data for the cursor (inclusive), or
+ * {@code null} for the first entry
+ * @param endRow the last row of data for the cursor (inclusive), or
+ * {@code null} for the last entry
+ */
+ public static CursorImpl createIndexCursor(TableImpl table, IndexImpl index,
+ Object[] startRow, Object[] endRow)
+ throws IOException
+ {
+ return IndexCursorImpl.createCursor(table, index, startRow, endRow);
+ }
+
+ /**
+ * Creates an indexed cursor for the given table, narrowed to the given
+ * range.
+ * <p>
+ * Note, index based table traversal may not include all rows, as certain
+ * types of indexes do not include all entries (namely, some indexes ignore
+ * null entries, see {@link Index#shouldIgnoreNulls}).
+ *
+ * @param table the table over which this cursor will traverse
+ * @param index index for the table which will define traversal order as
+ * well as enhance certain lookups
+ * @param startRow the first row of data for the cursor, or {@code null} for
+ * the first entry
+ * @param startInclusive whether or not startRow is inclusive or exclusive
+ * @param endRow the last row of data for the cursor, or {@code null} for
+ * the last entry
+ * @param endInclusive whether or not endRow is inclusive or exclusive
+ */
+ public static CursorImpl createIndexCursor(TableImpl table, IndexImpl index,
+ Object[] startRow,
+ boolean startInclusive,
+ Object[] endRow,
+ boolean endInclusive)
+ throws IOException
+ {
+ return IndexCursorImpl.createCursor(table, index, startRow, startInclusive,
+ endRow, endInclusive);
+ }
+
+ /**
+ * Convenience method for finding a specific row in a table which matches a
+ * given row "pattern". See {@link #findFirstRow(Map)} for details on the
+ * rowPattern.
+ * <p>
+ * Warning, this method <i>always</i> starts searching from the beginning of
+ * the Table (you cannot use it to find successive matches).
+ *
+ * @param table the table to search
+ * @param rowPattern pattern to be used to find the row
+ * @return the matching row or {@code null} if a match could not be found.
+ */
+ public static Map<String,Object> findRow(TableImpl table,
+ Map<String,?> rowPattern)
+ throws IOException
+ {
+ CursorImpl cursor = createCursor(table);
+ if(cursor.findFirstRow(rowPattern)) {
+ return cursor.getCurrentRow();
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method for finding a specific row in a table which matches a
+ * given row "pattern". See {@link #findFirstRow(Column,Object)} for
+ * details on the pattern.
+ * <p>
+ * Note, a {@code null} result value is ambiguous in that it could imply no
+ * match or a matching row with {@code null} for the desired value. If
+ * distinguishing this situation is important, you will need to use a Cursor
+ * directly instead of this convenience method.
+ *
+ * @param table the table to search
+ * @param column column whose value should be returned
+ * @param columnPattern column being matched by the valuePattern
+ * @param valuePattern value from the columnPattern which will match the
+ * desired row
+ * @return the matching row or {@code null} if a match could not be found.
+ */
+ public static Object findValue(TableImpl table, ColumnImpl column,
+ ColumnImpl columnPattern, Object valuePattern)
+ throws IOException
+ {
+ CursorImpl cursor = createCursor(table);
+ if(cursor.findFirstRow(columnPattern, valuePattern)) {
+ return cursor.getCurrentRowValue(column);
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method for finding a specific row in an indexed table which
+ * matches a given row "pattern". See {@link #findFirstRow(Map)} for
+ * details on the rowPattern.
+ * <p>
+ * Warning, this method <i>always</i> starts searching from the beginning of
+ * the Table (you cannot use it to find successive matches).
+ *
+ * @param table the table to search
+ * @param index index to assist the search
+ * @param rowPattern pattern to be used to find the row
+ * @return the matching row or {@code null} if a match could not be found.
+ */
+ public static Map<String,Object> findRow(TableImpl table, IndexImpl index,
+ Map<String,?> rowPattern)
+ throws IOException
+ {
+ CursorImpl cursor = createIndexCursor(table, index);
+ if(cursor.findFirstRow(rowPattern)) {
+ return cursor.getCurrentRow();
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method for finding a specific row in a table which matches a
+ * given row "pattern". See {@link #findFirstRow(Column,Object)} for
+ * details on the pattern.
+ * <p>
+ * Note, a {@code null} result value is ambiguous in that it could imply no
+ * match or a matching row with {@code null} for the desired value. If
+ * distinguishing this situation is important, you will need to use a Cursor
+ * directly instead of this convenience method.
+ *
+ * @param table the table to search
+ * @param index index to assist the search
+ * @param column column whose value should be returned
+ * @param columnPattern column being matched by the valuePattern
+ * @param valuePattern value from the columnPattern which will match the
+ * desired row
+ * @return the matching row or {@code null} if a match could not be found.
+ */
+ public static Object findValue(TableImpl table, IndexImpl index, ColumnImpl column,
+ ColumnImpl columnPattern, Object valuePattern)
+ throws IOException
+ {
+ CursorImpl cursor = createIndexCursor(table, index);
+ if(cursor.findFirstRow(columnPattern, valuePattern)) {
+ return cursor.getCurrentRowValue(column);
+ }
+ return null;
+ }
+
+ public IdImpl getId() {
+ return _id;
+ }
+
+ public TableImpl getTable() {
+ return _table;
+ }
+
+ public JetFormat getFormat() {
+ return getTable().getFormat();
+ }
+
+ public PageChannel getPageChannel() {
+ return getTable().getPageChannel();
+ }
+
+ public ErrorHandler getErrorHandler() {
+ return _rowState.getErrorHandler();
+ }
+
+ public void setErrorHandler(ErrorHandler newErrorHandler) {
+ _rowState.setErrorHandler(newErrorHandler);
+ }
+
+ public ColumnMatcher getColumnMatcher() {
+ return _columnMatcher;
+ }
+
+ public void setColumnMatcher(ColumnMatcher columnMatcher) {
+ if(columnMatcher == null) {
+ columnMatcher = getDefaultColumnMatcher();
+ }
+ _columnMatcher = columnMatcher;
+ }
+
+ /**
+ * Returns the default ColumnMatcher for this Cursor.
+ */
+ protected ColumnMatcher getDefaultColumnMatcher() {
+ return SimpleColumnMatcher.INSTANCE;
+ }
+
+ public SavepointImpl getSavepoint() {
+ return new SavepointImpl(_id, _curPos, _prevPos);
+ }
+
+ public void restoreSavepoint(Savepoint savepoint)
+ throws IOException
+ {
+ restoreSavepoint((SavepointImpl)savepoint);
+ }
+
+ public void restoreSavepoint(SavepointImpl savepoint)
+ throws IOException
+ {
+ if(!_id.equals(savepoint.getCursorId())) {
+ throw new IllegalArgumentException(
+ "Savepoint " + savepoint + " is not valid for this cursor with id "
+ + _id);
+ }
+ restorePosition(savepoint.getCurrentPosition(),
+ savepoint.getPreviousPosition());
+ }
+
+ /**
+ * Returns the first row id (exclusive) as defined by this cursor.
+ */
+ protected PositionImpl getFirstPosition() {
+ return _firstPos;
+ }
+
+ /**
+ * Returns the last row id (exclusive) as defined by this cursor.
+ */
+ protected PositionImpl getLastPosition() {
+ return _lastPos;
+ }
+
+ public void reset() {
+ beforeFirst();
+ }
+
+ public void beforeFirst() {
+ reset(MOVE_FORWARD);
+ }
+
+ public void afterLast() {
+ reset(MOVE_REVERSE);
+ }
+
+ public boolean isBeforeFirst() throws IOException
+ {
+ if(getFirstPosition().equals(_curPos)) {
+ return !recheckPosition(MOVE_REVERSE);
+ }
+ return false;
+ }
+
+ public boolean isAfterLast() throws IOException
+ {
+ if(getLastPosition().equals(_curPos)) {
+ return !recheckPosition(MOVE_FORWARD);
+ }
+ return false;
+ }
+
+ public boolean isCurrentRowDeleted() throws IOException
+ {
+ // we need to ensure that the "deleted" flag has been read for this row
+ // (or re-read if the table has been recently modified)
+ TableImpl.positionAtRowData(_rowState, _curPos.getRowId());
+ return _rowState.isDeleted();
+ }
+
+ /**
+ * Resets this cursor for traversing the given direction.
+ */
+ protected void reset(boolean moveForward) {
+ _curPos = getDirHandler(moveForward).getBeginningPosition();
+ _prevPos = _curPos;
+ _rowState.reset();
+ }
+
+ public Iterable<Map<String, Object>> reverseIterable() {
+ return reverseIterable(null);
+ }
+
+ public Iterable<Map<String, Object>> reverseIterable(
+ final Collection<String> columnNames)
+ {
+ return new Iterable<Map<String, Object>>() {
+ public Iterator<Map<String, Object>> iterator() {
+ return new RowIterator(columnNames, MOVE_REVERSE);
+ }
+ };
+ }
+
+ public Iterator<Map<String, Object>> iterator()
+ {
+ return iterator(null);
+ }
+
+ public Iterable<Map<String, Object>> iterable(
+ final Collection<String> columnNames)
+ {
+ return new Iterable<Map<String, Object>>() {
+ public Iterator<Map<String, Object>> iterator() {
+ return CursorImpl.this.iterator(columnNames);
+ }
+ };
+ }
+
+ public Iterator<Map<String, Object>> iterator(Collection<String> columnNames)
+ {
+ return new RowIterator(columnNames, MOVE_FORWARD);
+ }
+
+ public Iterable<Map<String, Object>> columnMatchIterable(
+ Column columnPattern, Object valuePattern)
+ {
+ return columnMatchIterable((ColumnImpl)columnPattern, valuePattern);
+ }
+
+ public Iterable<Map<String, Object>> columnMatchIterable(
+ ColumnImpl columnPattern, Object valuePattern)
+ {
+ return columnMatchIterable(null, columnPattern, valuePattern);
+ }
+
+ public Iterator<Map<String, Object>> columnMatchIterator(
+ Column columnPattern, Object valuePattern)
+ {
+ return columnMatchIterator((ColumnImpl)columnPattern, valuePattern);
+ }
+
+ public Iterator<Map<String, Object>> columnMatchIterator(
+ ColumnImpl columnPattern, Object valuePattern)
+ {
+ return columnMatchIterator(null, columnPattern, valuePattern);
+ }
+
+ public Iterable<Map<String, Object>> columnMatchIterable(
+ Collection<String> columnNames,
+ Column columnPattern, Object valuePattern)
+ {
+ return columnMatchIterable(columnNames, (ColumnImpl)columnPattern,
+ valuePattern);
+ }
+
+ public Iterable<Map<String, Object>> columnMatchIterable(
+ final Collection<String> columnNames,
+ final ColumnImpl columnPattern, final Object valuePattern)
+ {
+ return new Iterable<Map<String, Object>>() {
+ public Iterator<Map<String, Object>> iterator() {
+ return CursorImpl.this.columnMatchIterator(
+ columnNames, columnPattern, valuePattern);
+ }
+ };
+ }
+
+ public Iterator<Map<String, Object>> columnMatchIterator(
+ Collection<String> columnNames, Column columnPattern,
+ Object valuePattern)
+ {
+ return columnMatchIterator(columnNames, (ColumnImpl)columnPattern,
+ valuePattern);
+ }
+
+ public Iterator<Map<String, Object>> columnMatchIterator(
+ Collection<String> columnNames, ColumnImpl columnPattern,
+ Object valuePattern)
+ {
+ return new ColumnMatchIterator(columnNames, columnPattern, valuePattern);
+ }
+
+ public Iterable<Map<String, Object>> rowMatchIterable(
+ Map<String,?> rowPattern)
+ {
+ return rowMatchIterable(null, rowPattern);
+ }
+
+ public Iterator<Map<String, Object>> rowMatchIterator(
+ Map<String,?> rowPattern)
+ {
+ return rowMatchIterator(null, rowPattern);
+ }
+
+ public Iterable<Map<String, Object>> rowMatchIterable(
+ final Collection<String> columnNames,
+ final Map<String,?> rowPattern)
+ {
+ return new Iterable<Map<String, Object>>() {
+ public Iterator<Map<String, Object>> iterator() {
+ return CursorImpl.this.rowMatchIterator(
+ columnNames, rowPattern);
+ }
+ };
+ }
+
+ public Iterator<Map<String, Object>> rowMatchIterator(
+ Collection<String> columnNames, Map<String,?> rowPattern)
+ {
+ return new RowMatchIterator(columnNames, rowPattern);
+ }
+
+ public void deleteCurrentRow() throws IOException {
+ _table.deleteRow(_rowState, _curPos.getRowId());
+ }
+
+ public void updateCurrentRow(Object... row) throws IOException {
+ _table.updateRow(_rowState, _curPos.getRowId(), row);
+ }
+
+ public Map<String, Object> getNextRow() throws IOException {
+ return getNextRow(null);
+ }
+
+ public Map<String, Object> getNextRow(Collection<String> columnNames)
+ throws IOException
+ {
+ return getAnotherRow(columnNames, MOVE_FORWARD);
+ }
+
+ public Map<String, Object> getPreviousRow() throws IOException {
+ return getPreviousRow(null);
+ }
+
+ public Map<String, Object> getPreviousRow(Collection<String> columnNames)
+ throws IOException
+ {
+ return getAnotherRow(columnNames, MOVE_REVERSE);
+ }
+
+
+ /**
+ * Moves to another row in the table based on the given direction and
+ * returns it.
+ * @param columnNames Only column names in this collection will be returned
+ * @return another row in this table (Column name -> Column value), where
+ * "next" may be backwards if moveForward is {@code false}, or
+ * {@code null} if there is not another row in the given direction.
+ */
+ private Map<String, Object> getAnotherRow(Collection<String> columnNames,
+ boolean moveForward)
+ throws IOException
+ {
+ if(moveToAnotherRow(moveForward)) {
+ return getCurrentRow(columnNames);
+ }
+ return null;
+ }
+
+ public boolean moveToNextRow() throws IOException
+ {
+ return moveToAnotherRow(MOVE_FORWARD);
+ }
+
+ public boolean moveToPreviousRow() throws IOException
+ {
+ return moveToAnotherRow(MOVE_REVERSE);
+ }
+
+ /**
+ * Moves to another row in the given direction as defined by this cursor.
+ * @return {@code true} if another valid row was found in the given
+ * direction, {@code false} otherwise
+ */
+ private boolean moveToAnotherRow(boolean moveForward)
+ throws IOException
+ {
+ if(_curPos.equals(getDirHandler(moveForward).getEndPosition())) {
+ // already at end, make sure nothing has changed
+ return recheckPosition(moveForward);
+ }
+
+ return moveToAnotherRowImpl(moveForward);
+ }
+
+ /**
+ * Restores a current position for the cursor (current position becomes
+ * previous position).
+ */
+ protected void restorePosition(PositionImpl curPos)
+ throws IOException
+ {
+ restorePosition(curPos, _curPos);
+ }
+
+ /**
+ * Restores a current and previous position for the cursor if the given
+ * positions are different from the current positions.
+ */
+ protected final void restorePosition(PositionImpl curPos,
+ PositionImpl prevPos)
+ throws IOException
+ {
+ if(!curPos.equals(_curPos) || !prevPos.equals(_prevPos)) {
+ restorePositionImpl(curPos, prevPos);
+ }
+ }
+
+ /**
+ * Restores a current and previous position for the cursor.
+ */
+ protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
+ throws IOException
+ {
+ // make the current position previous, and the new position current
+ _prevPos = _curPos;
+ _curPos = curPos;
+ _rowState.reset();
+ }
+
+ /**
+ * Rechecks the current position if the underlying data structures have been
+ * modified.
+ * @return {@code true} if the cursor ended up in a new position,
+ * {@code false} otherwise.
+ */
+ private boolean recheckPosition(boolean moveForward)
+ throws IOException
+ {
+ if(isUpToDate()) {
+ // nothing has changed
+ return false;
+ }
+
+ // move the cursor back to the previous position
+ restorePosition(_prevPos);
+ return moveToAnotherRowImpl(moveForward);
+ }
+
+ /**
+ * Does the grunt work of moving the cursor to another position in the given
+ * direction.
+ */
+ private boolean moveToAnotherRowImpl(boolean moveForward)
+ throws IOException
+ {
+ _rowState.reset();
+ _prevPos = _curPos;
+ _curPos = findAnotherPosition(_rowState, _curPos, moveForward);
+ TableImpl.positionAtRowHeader(_rowState, _curPos.getRowId());
+ return(!_curPos.equals(getDirHandler(moveForward).getEndPosition()));
+ }
+
+ public boolean findFirstRow(Column columnPattern, Object valuePattern)
+ throws IOException
+ {
+ return findFirstRow((ColumnImpl)columnPattern, valuePattern);
+ }
+
+ public boolean findFirstRow(ColumnImpl columnPattern, Object valuePattern)
+ throws IOException
+ {
+ PositionImpl curPos = _curPos;
+ PositionImpl prevPos = _prevPos;
+ boolean found = false;
+ try {
+ beforeFirst();
+ found = findNextRowImpl(columnPattern, valuePattern);
+ return found;
+ } finally {
+ if(!found) {
+ try {
+ restorePosition(curPos, prevPos);
+ } catch(IOException e) {
+ LOG.error("Failed restoring position", e);
+ }
+ }
+ }
+ }
+
+ public boolean findNextRow(Column columnPattern, Object valuePattern)
+ throws IOException
+ {
+ return findNextRow((ColumnImpl)columnPattern, valuePattern);
+ }
+
+ public boolean findNextRow(ColumnImpl columnPattern, Object valuePattern)
+ throws IOException
+ {
+ PositionImpl curPos = _curPos;
+ PositionImpl prevPos = _prevPos;
+ boolean found = false;
+ try {
+ found = findNextRowImpl(columnPattern, valuePattern);
+ return found;
+ } finally {
+ if(!found) {
+ try {
+ restorePosition(curPos, prevPos);
+ } catch(IOException e) {
+ LOG.error("Failed restoring position", e);
+ }
+ }
+ }
+ }
+
+ public boolean findFirstRow(Map<String,?> rowPattern) throws IOException
+ {
+ PositionImpl curPos = _curPos;
+ PositionImpl prevPos = _prevPos;
+ boolean found = false;
+ try {
+ beforeFirst();
+ found = findNextRowImpl(rowPattern);
+ return found;
+ } finally {
+ if(!found) {
+ try {
+ restorePosition(curPos, prevPos);
+ } catch(IOException e) {
+ LOG.error("Failed restoring position", e);
+ }
+ }
+ }
+ }
+
+ public boolean findNextRow(Map<String,?> rowPattern)
+ throws IOException
+ {
+ PositionImpl curPos = _curPos;
+ PositionImpl prevPos = _prevPos;
+ boolean found = false;
+ try {
+ found = findNextRowImpl(rowPattern);
+ return found;
+ } finally {
+ if(!found) {
+ try {
+ restorePosition(curPos, prevPos);
+ } catch(IOException e) {
+ LOG.error("Failed restoring position", e);
+ }
+ }
+ }
+ }
+
+ public boolean currentRowMatches(Column columnPattern, Object valuePattern)
+ throws IOException
+ {
+ return currentRowMatches((ColumnImpl)columnPattern, valuePattern);
+ }
+
+ public boolean currentRowMatches(ColumnImpl columnPattern, Object valuePattern)
+ throws IOException
+ {
+ return _columnMatcher.matches(getTable(), columnPattern.getName(),
+ valuePattern,
+ getCurrentRowValue(columnPattern));
+ }
+
+ public boolean currentRowMatches(Map<String,?> rowPattern)
+ throws IOException
+ {
+ Map<String,Object> row = getCurrentRow(rowPattern.keySet());
+
+ if(rowPattern.size() != row.size()) {
+ return false;
+ }
+
+ for(Map.Entry<String,Object> e : row.entrySet()) {
+ String columnName = e.getKey();
+ if(!_columnMatcher.matches(getTable(), columnName,
+ rowPattern.get(columnName), e.getValue())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Moves to the next row (as defined by the cursor) where the given column
+ * has the given value. Caller manages save/restore on failure.
+ * <p>
+ * Default implementation scans the table from beginning to end.
+ *
+ * @param columnPattern column from the table for this cursor which is being
+ * matched by the valuePattern
+ * @param valuePattern value which is equal to the corresponding value in
+ * the matched row
+ * @return {@code true} if a valid row was found with the given value,
+ * {@code false} if no row was found
+ */
+ protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
+ throws IOException
+ {
+ while(moveToNextRow()) {
+ if(currentRowMatches(columnPattern, valuePattern)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Moves to the next row (as defined by the cursor) where the given columns
+ * have the given values. Caller manages save/restore on failure.
+ * <p>
+ * Default implementation scans the table from beginning to end.
+ *
+ * @param rowPattern column names and values which must be equal to the
+ * corresponding values in the matched row
+ * @return {@code true} if a valid row was found with the given values,
+ * {@code false} if no row was found
+ */
+ protected boolean findNextRowImpl(Map<String,?> rowPattern)
+ throws IOException
+ {
+ while(moveToNextRow()) {
+ if(currentRowMatches(rowPattern)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int moveNextRows(int numRows) throws IOException
+ {
+ return moveSomeRows(numRows, MOVE_FORWARD);
+ }
+
+ public int movePreviousRows(int numRows) throws IOException
+ {
+ return moveSomeRows(numRows, MOVE_REVERSE);
+ }
+
+ /**
+ * Moves as many rows as possible in the given direction up to the given
+ * number of rows.
+ * @return the number of rows moved.
+ */
+ private int moveSomeRows(int numRows, boolean moveForward)
+ throws IOException
+ {
+ int numMovedRows = 0;
+ while((numMovedRows < numRows) && moveToAnotherRow(moveForward)) {
+ ++numMovedRows;
+ }
+ return numMovedRows;
+ }
+
+ public Map<String, Object> getCurrentRow() throws IOException
+ {
+ return getCurrentRow(null);
+ }
+
+ public Map<String, Object> getCurrentRow(Collection<String> columnNames)
+ throws IOException
+ {
+ return _table.getRow(_rowState, _curPos.getRowId(), columnNames);
+ }
+
+ public Object getCurrentRowValue(Column column)
+ throws IOException
+ {
+ return getCurrentRowValue((ColumnImpl)column);
+ }
+
+ public Object getCurrentRowValue(ColumnImpl column)
+ throws IOException
+ {
+ return _table.getRowValue(_rowState, _curPos.getRowId(), column);
+ }
+
+ public void setCurrentRowValue(Column column, Object value)
+ throws IOException
+ {
+ setCurrentRowValue((ColumnImpl)column, value);
+ }
+
+ public void setCurrentRowValue(ColumnImpl column, Object value)
+ throws IOException
+ {
+ Object[] row = new Object[_table.getColumnCount()];
+ Arrays.fill(row, Column.KEEP_VALUE);
+ column.setRowValue(row, value);
+ _table.updateRow(_rowState, _curPos.getRowId(), row);
+ }
+
+ /**
+ * Returns {@code true} if this cursor is up-to-date with respect to the
+ * relevant table and related table objects, {@code false} otherwise.
+ */
+ protected boolean isUpToDate() {
+ return _rowState.isUpToDate();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " CurPosition " + _curPos +
+ ", PrevPosition " + _prevPos;
+ }
+
+ /**
+ * Finds the next non-deleted row after the given row (as defined by this
+ * cursor) and returns the id of the row, where "next" may be backwards if
+ * moveForward is {@code false}. If there are no more rows, the returned
+ * rowId should equal the value returned by {@link #getLastPosition} if
+ * moving forward and {@link #getFirstPosition} if moving backward.
+ */
+ protected abstract PositionImpl findAnotherPosition(RowState rowState,
+ PositionImpl curPos,
+ boolean moveForward)
+ throws IOException;
+
+ /**
+ * Returns the DirHandler for the given movement direction.
+ */
+ protected abstract DirHandler getDirHandler(boolean moveForward);
+
+
+ /**
+ * Base implementation of iterator for this cursor, modifiable.
+ */
+ protected abstract class BaseIterator
+ implements Iterator<Map<String, Object>>
+ {
+ protected final Collection<String> _columnNames;
+ protected Boolean _hasNext;
+ protected boolean _validRow;
+
+ protected BaseIterator(Collection<String> columnNames)
+ {
+ _columnNames = columnNames;
+ }
+
+ public boolean hasNext() {
+ if(_hasNext == null) {
+ try {
+ _hasNext = findNext();
+ _validRow = _hasNext;
+ } catch(IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ return _hasNext;
+ }
+
+ public Map<String, Object> next() {
+ if(!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ try {
+ Map<String, Object> rtn = getCurrentRow(_columnNames);
+ _hasNext = null;
+ return rtn;
+ } catch(IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public void remove() {
+ if(_validRow) {
+ try {
+ deleteCurrentRow();
+ _validRow = false;
+ } catch(IOException e) {
+ throw new IllegalStateException(e);
+ }
+ } else {
+ throw new IllegalStateException("Not at valid row");
+ }
+ }
+
+ protected abstract boolean findNext() throws IOException;
+ }
+
+
+ /**
+ * Row iterator for this cursor, modifiable.
+ */
+ private final class RowIterator extends BaseIterator
+ {
+ private final boolean _moveForward;
+
+ private RowIterator(Collection<String> columnNames, boolean moveForward)
+ {
+ super(columnNames);
+ _moveForward = moveForward;
+ reset(_moveForward);
+ }
+
+ @Override
+ protected boolean findNext() throws IOException {
+ return moveToAnotherRow(_moveForward);
+ }
+ }
+
+
+ /**
+ * Row iterator for this cursor, modifiable.
+ */
+ private final class ColumnMatchIterator extends BaseIterator
+ {
+ private final ColumnImpl _columnPattern;
+ private final Object _valuePattern;
+
+ private ColumnMatchIterator(Collection<String> columnNames,
+ ColumnImpl columnPattern, Object valuePattern)
+ {
+ super(columnNames);
+ _columnPattern = columnPattern;
+ _valuePattern = valuePattern;
+ beforeFirst();
+ }
+
+ @Override
+ protected boolean findNext() throws IOException {
+ return findNextRow(_columnPattern, _valuePattern);
+ }
+ }
+
+
+ /**
+ * Row iterator for this cursor, modifiable.
+ */
+ private final class RowMatchIterator extends BaseIterator
+ {
+ private final Map<String,?> _rowPattern;
+
+ private RowMatchIterator(Collection<String> columnNames,
+ Map<String,?> rowPattern)
+ {
+ super(columnNames);
+ _rowPattern = rowPattern;
+ beforeFirst();
+ }
+
+ @Override
+ protected boolean findNext() throws IOException {
+ return findNextRow(_rowPattern);
+ }
+ }
+
+
+ /**
+ * Handles moving the cursor in a given direction. Separates cursor
+ * logic from value storage.
+ */
+ protected abstract class DirHandler
+ {
+ public abstract PositionImpl getBeginningPosition();
+ public abstract PositionImpl getEndPosition();
+ }
+
+
+ /**
+ * Simple un-indexed cursor.
+ */
+ private static final class TableScanCursor extends CursorImpl
+ {
+ /** ScanDirHandler for forward traversal */
+ private final ScanDirHandler _forwardDirHandler =
+ new ForwardScanDirHandler();
+ /** ScanDirHandler for backward traversal */
+ private final ScanDirHandler _reverseDirHandler =
+ new ReverseScanDirHandler();
+ /** Cursor over the pages that this table owns */
+ private final UsageMap.PageCursor _ownedPagesCursor;
+
+ private TableScanCursor(TableImpl table) {
+ super(new IdImpl(table, null), table,
+ FIRST_SCAN_POSITION, LAST_SCAN_POSITION);
+ _ownedPagesCursor = table.getOwnedPagesCursor();
+ }
+
+ @Override
+ protected ScanDirHandler getDirHandler(boolean moveForward) {
+ return (moveForward ? _forwardDirHandler : _reverseDirHandler);
+ }
+
+ @Override
+ protected boolean isUpToDate() {
+ return(super.isUpToDate() && _ownedPagesCursor.isUpToDate());
+ }
+
+ @Override
+ protected void reset(boolean moveForward) {
+ _ownedPagesCursor.reset(moveForward);
+ super.reset(moveForward);
+ }
+
+ @Override
+ protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
+ throws IOException
+ {
+ if(!(curPos instanceof ScanPosition) ||
+ !(prevPos instanceof ScanPosition)) {
+ throw new IllegalArgumentException(
+ "Restored positions must be scan positions");
+ }
+ _ownedPagesCursor.restorePosition(curPos.getRowId().getPageNumber(),
+ prevPos.getRowId().getPageNumber());
+ super.restorePositionImpl(curPos, prevPos);
+ }
+
+ @Override
+ protected PositionImpl findAnotherPosition(
+ RowState rowState, PositionImpl curPos, boolean moveForward)
+ throws IOException
+ {
+ ScanDirHandler handler = getDirHandler(moveForward);
+
+ // figure out how many rows are left on this page so we can find the
+ // next row
+ RowId curRowId = curPos.getRowId();
+ TableImpl.positionAtRowHeader(rowState, curRowId);
+ int currentRowNumber = curRowId.getRowNumber();
+
+ // loop until we find the next valid row or run out of pages
+ while(true) {
+
+ currentRowNumber = handler.getAnotherRowNumber(currentRowNumber);
+ curRowId = new RowId(curRowId.getPageNumber(), currentRowNumber);
+ TableImpl.positionAtRowHeader(rowState, curRowId);
+
+ if(!rowState.isValid()) {
+
+ // load next page
+ curRowId = new RowId(handler.getAnotherPageNumber(),
+ RowId.INVALID_ROW_NUMBER);
+ TableImpl.positionAtRowHeader(rowState, curRowId);
+
+ if(!rowState.isHeaderPageNumberValid()) {
+ //No more owned pages. No more rows.
+ return handler.getEndPosition();
+ }
+
+ // update row count and initial row number
+ currentRowNumber = handler.getInitialRowNumber(
+ rowState.getRowsOnHeaderPage());
+
+ } else if(!rowState.isDeleted()) {
+
+ // we found a valid, non-deleted row, return it
+ return new ScanPosition(curRowId);
+ }
+
+ }
+ }
+
+ /**
+ * Handles moving the table scan cursor in a given direction. Separates
+ * cursor logic from value storage.
+ */
+ private abstract class ScanDirHandler extends DirHandler {
+ public abstract int getAnotherRowNumber(int curRowNumber);
+ public abstract int getAnotherPageNumber();
+ public abstract int getInitialRowNumber(int rowsOnPage);
+ }
+
+ /**
+ * Handles moving the table scan cursor forward.
+ */
+ private final class ForwardScanDirHandler extends ScanDirHandler {
+ @Override
+ public PositionImpl getBeginningPosition() {
+ return getFirstPosition();
+ }
+ @Override
+ public PositionImpl getEndPosition() {
+ return getLastPosition();
+ }
+ @Override
+ public int getAnotherRowNumber(int curRowNumber) {
+ return curRowNumber + 1;
+ }
+ @Override
+ public int getAnotherPageNumber() {
+ return _ownedPagesCursor.getNextPage();
+ }
+ @Override
+ public int getInitialRowNumber(int rowsOnPage) {
+ return -1;
+ }
+ }
+
+ /**
+ * Handles moving the table scan cursor backward.
+ */
+ private final class ReverseScanDirHandler extends ScanDirHandler {
+ @Override
+ public PositionImpl getBeginningPosition() {
+ return getLastPosition();
+ }
+ @Override
+ public PositionImpl getEndPosition() {
+ return getFirstPosition();
+ }
+ @Override
+ public int getAnotherRowNumber(int curRowNumber) {
+ return curRowNumber - 1;
+ }
+ @Override
+ public int getAnotherPageNumber() {
+ return _ownedPagesCursor.getPreviousPage();
+ }
+ @Override
+ public int getInitialRowNumber(int rowsOnPage) {
+ return rowsOnPage;
+ }
+ }
+
+ }
+
+
+ /**
+ * Identifier for a cursor. Will be equal to any other cursor of the same
+ * type for the same table. Primarily used to check the validity of a
+ * Savepoint.
+ */
+ protected static final class IdImpl implements Id
+ {
+ private final String _tableName;
+ private final String _indexName;
+
+ protected IdImpl(TableImpl table, Index index) {
+ _tableName = table.getName();
+ _indexName = ((index != null) ? index.getName() : null);
+ }
+
+ @Override
+ public int hashCode() {
+ return _tableName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return((this == o) ||
+ ((o != null) && (getClass() == o.getClass()) &&
+ ObjectUtils.equals(_tableName, ((IdImpl)o)._tableName) &&
+ ObjectUtils.equals(_indexName, ((IdImpl)o)._indexName)));
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " " + _tableName + ":" + _indexName;
+ }
+ }
+
+ /**
+ * Value object which maintains the current position of the cursor.
+ */
+ protected static abstract class PositionImpl implements Position
+ {
+ protected PositionImpl() {
+ }
+
+ @Override
+ public final int hashCode() {
+ return getRowId().hashCode();
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ return((this == o) ||
+ ((o != null) && (getClass() == o.getClass()) && equalsImpl(o)));
+ }
+
+ /**
+ * Returns the unique RowId of the position of the cursor.
+ */
+ public abstract RowId getRowId();
+
+ /**
+ * Returns {@code true} if the subclass specific info in a Position is
+ * equal, {@code false} otherwise.
+ * @param o object being tested for equality, guaranteed to be the same
+ * class as this object
+ */
+ protected abstract boolean equalsImpl(Object o);
+ }
+
+ /**
+ * Value object which represents a complete save state of the cursor.
+ */
+ protected static final class SavepointImpl implements Savepoint
+ {
+ private final IdImpl _cursorId;
+ private final PositionImpl _curPos;
+ private final PositionImpl _prevPos;
+
+ private SavepointImpl(IdImpl cursorId, PositionImpl curPos,
+ PositionImpl prevPos) {
+ _cursorId = cursorId;
+ _curPos = curPos;
+ _prevPos = prevPos;
+ }
+
+ public IdImpl getCursorId() {
+ return _cursorId;
+ }
+
+ public PositionImpl getCurrentPosition() {
+ return _curPos;
+ }
+
+ private PositionImpl getPreviousPosition() {
+ return _prevPos;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " " + _cursorId + " CurPosition " +
+ _curPos + ", PrevPosition " + _prevPos;
+ }
+ }
+
+ /**
+ * Value object which maintains the current position of a TableScanCursor.
+ */
+ private static final class ScanPosition extends PositionImpl
+ {
+ private final RowId _rowId;
+
+ private ScanPosition(RowId rowId) {
+ _rowId = rowId;
+ }
+
+ @Override
+ public RowId getRowId() {
+ return _rowId;
+ }
+
+ @Override
+ protected boolean equalsImpl(Object o) {
+ return getRowId().equals(((ScanPosition)o).getRowId());
+ }
+
+ @Override
+ public String toString() {
+ return "RowId = " + getRowId();
+ }
+ }
+
+}
List<Relationship> relationships = new ArrayList<Relationship>();
- Cursor cursor = createCursorWithOptionalIndex(
+ CursorImpl cursor = createCursorWithOptionalIndex(
_relationships, REL_COL_FROM_TABLE, table1.getName());
collectRelationships(cursor, table1, table2, relationships);
cursor = createCursorWithOptionalIndex(
Map<Integer,List<Query.Row>> queryRowMap =
new HashMap<Integer,List<Query.Row>>();
for(Map<String,Object> row :
- Cursor.createCursor(_systemCatalog).iterable(SYSTEM_CATALOG_COLUMNS))
+ CursorImpl.createCursor(_systemCatalog).iterable(SYSTEM_CATALOG_COLUMNS))
{
String name = (String) row.get(CAT_COL_NAME);
if (name != null && TYPE_QUERY.equals(row.get(CAT_COL_TYPE))) {
}
// find all the query rows
- for(Map<String,Object> row : Cursor.createCursor(_queries)) {
+ for(Map<String,Object> row : CursorImpl.createCursor(_queries)) {
Query.Row queryRow = new Query.Row(row);
List<Query.Row> queryRows = queryRowMap.get(queryRow.objectId);
if(queryRows == null) {
* given cursor and adds them to the given list.
*/
private static void collectRelationships(
- Cursor cursor, TableImpl fromTable, TableImpl toTable,
+ CursorImpl cursor, TableImpl fromTable, TableImpl toTable,
List<Relationship> relationships)
{
for(Map<String,Object> row : cursor) {
{
// search for ACEs matching the tableParentId. use the index on the
// objectId column if found (should be there)
- Cursor cursor = createCursorWithOptionalIndex(
+ CursorImpl cursor = createCursorWithOptionalIndex(
getAccessControlEntries(), ACE_COL_OBJECT_ID, _tableParentId);
for(Map<String, Object> row : cursor) {
* 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
{
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 {
public Integer findObjectId(Integer parentId, String name)
throws IOException
{
- Cursor cur = findRow(parentId, name);
+ CursorImpl cur = findRow(parentId, name);
if(cur == null) {
return null;
}
Collection<String> columns)
throws IOException
{
- Cursor cur = findRow(parentId, name);
+ CursorImpl cur = findRow(parentId, name);
return ((cur != null) ? cur.getCurrentRow(columns) : null);
}
Integer objectId, Collection<String> columns)
throws IOException
{
- Cursor cur = findRow(objectId);
+ CursorImpl cur = findRow(objectId);
return ((cur != null) ? cur.getCurrentRow(columns) : null);
}
}
}
- 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;
*/
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;
}
}
@Override
- protected Cursor findRow(Integer parentId, String name)
+ protected CursorImpl findRow(Integer parentId, String name)
throws IOException
{
return (_systemCatalogCursor.findFirstRowByEntry(parentId, name) ?
}
@Override
- protected Cursor findRow(Integer objectId) throws IOException
+ protected CursorImpl findRow(Integer objectId) throws IOException
{
initIdCursor();
return (_systemCatalogIdCursor.findFirstRowByEntry(objectId) ?
}
@Override
- protected Cursor getTableNamesCursor() throws IOException {
+ protected CursorImpl getTableNamesCursor() throws IOException {
return new CursorBuilder(_systemCatalog)
.setIndex(_systemCatalogCursor.getIndex())
.setStartEntry(_tableParentId, IndexData.MIN_VALUE)
*/
private final class FallbackTableFinder extends TableFinder
{
- private final Cursor _systemCatalogCursor;
+ private final CursorImpl _systemCatalogCursor;
- private FallbackTableFinder(Cursor systemCatalogCursor) {
+ private FallbackTableFinder(CursorImpl systemCatalogCursor) {
_systemCatalogCursor = systemCatalogCursor;
}
@Override
- protected Cursor findRow(Integer parentId, String name)
+ protected CursorImpl findRow(Integer parentId, String name)
throws IOException
{
Map<String,Object> rowPat = new HashMap<String,Object>();
}
@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) ?
}
@Override
- protected Cursor getTableNamesCursor() throws IOException {
+ protected CursorImpl getTableNamesCursor() throws IOException {
return _systemCatalogCursor;
}
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);
}
*
* @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
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;
_tableName = tableName;
}
- public Builder(Cursor cursor) {
+ public Builder(CursorImpl cursor) {
_cursor = cursor;
}
return this;
}
- public Builder setCursor(Cursor cursor) {
+ public Builder setCursor(CursorImpl cursor) {
_cursor = cursor;
return this;
}
Object[] newFromRow)
throws IOException
{
- IndexCursor toCursor = joiner.getToCursor();
+ IndexCursorImpl toCursor = joiner.getToCursor();
List<IndexData.ColumnDescriptor> fromCols = joiner.getColumns();
List<IndexData.ColumnDescriptor> toCols = joiner.getToIndex().getColumns();
Object[] toRow = new Object[joiner.getToTable().getColumnCount()];
/*
-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
import java.io.IOException;
import java.util.Collection;
-import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.Map;
-import java.util.Set;
-
-import com.healthmarketscience.jackcess.TableImpl.RowState;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
/**
* Cursor backed by an index with extended traversal options.
*
* @author James Ahlborn
*/
-public class IndexCursor extends Cursor
+public interface IndexCursor extends Cursor
{
- private static final Log LOG = LogFactory.getLog(IndexCursor.class);
-
- /** IndexDirHandler for forward traversal */
- private final IndexDirHandler _forwardDirHandler =
- new ForwardIndexDirHandler();
- /** IndexDirHandler for backward traversal */
- private final IndexDirHandler _reverseDirHandler =
- new ReverseIndexDirHandler();
- /** logical index which this cursor is using */
- private final IndexImpl _index;
- /** Cursor over the entries of the relevant index */
- private final IndexData.EntryCursor _entryCursor;
- /** column names for the index entry columns */
- private Set<String> _indexEntryPattern;
- private IndexCursor(TableImpl table, IndexImpl index,
- IndexData.EntryCursor entryCursor)
- throws IOException
- {
- super(new Id(table, index), table,
- new IndexPosition(entryCursor.getFirstEntry()),
- new IndexPosition(entryCursor.getLastEntry()));
- _index = index;
- _index.initialize();
- _entryCursor = entryCursor;
- }
-
- /**
- * Creates an indexed cursor for the given table.
- * <p>
- * Note, index based table traversal may not include all rows, as certain
- * types of indexes do not include all entries (namely, some indexes ignore
- * null entries, see {@link Index#shouldIgnoreNulls}).
- *
- * @param table the table over which this cursor will traverse
- * @param index index for the table which will define traversal order as
- * well as enhance certain lookups
- */
- public static IndexCursor createCursor(TableImpl table, IndexImpl index)
- throws IOException
- {
- return createCursor(table, index, null, null);
- }
-
- /**
- * Creates an indexed cursor for the given table, narrowed to the given
- * range.
- * <p>
- * Note, index based table traversal may not include all rows, as certain
- * types of indexes do not include all entries (namely, some indexes ignore
- * null entries, see {@link Index#shouldIgnoreNulls}).
- *
- * @param table the table over which this cursor will traverse
- * @param index index for the table which will define traversal order as
- * well as enhance certain lookups
- * @param startRow the first row of data for the cursor (inclusive), or
- * {@code null} for the first entry
- * @param endRow the last row of data for the cursor (inclusive), or
- * {@code null} for the last entry
- */
- public static IndexCursor createCursor(
- TableImpl table, IndexImpl index, Object[] startRow, Object[] endRow)
- throws IOException
- {
- return createCursor(table, index, startRow, true, endRow, true);
- }
-
- /**
- * Creates an indexed cursor for the given table, narrowed to the given
- * range.
- * <p>
- * Note, index based table traversal may not include all rows, as certain
- * types of indexes do not include all entries (namely, some indexes ignore
- * null entries, see {@link Index#shouldIgnoreNulls}).
- *
- * @param table the table over which this cursor will traverse
- * @param index index for the table which will define traversal order as
- * well as enhance certain lookups
- * @param startRow the first row of data for the cursor, or {@code null} for
- * the first entry
- * @param startInclusive whether or not startRow is inclusive or exclusive
- * @param endRow the last row of data for the cursor, or {@code null} for
- * the last entry
- * @param endInclusive whether or not endRow is inclusive or exclusive
- */
- public static IndexCursor createCursor(TableImpl table, IndexImpl index,
- Object[] startRow,
- boolean startInclusive,
- Object[] endRow,
- boolean endInclusive)
- throws IOException
- {
- if(table != index.getTable()) {
- throw new IllegalArgumentException(
- "Given index is not for given table: " + index + ", " + table);
- }
- if(!table.getFormat().INDEXES_SUPPORTED) {
- throw new IllegalArgumentException(
- "JetFormat " + table.getFormat() +
- " does not currently support index lookups");
- }
- if(index.getIndexData().isReadOnly()) {
- throw new IllegalArgumentException(
- "Given index " + index +
- " is not usable for indexed lookups because it is read-only");
- }
- IndexCursor cursor = new IndexCursor(table, index,
- index.cursor(startRow, startInclusive,
- endRow, endInclusive));
- // init the column matcher appropriately for the index type
- cursor.setColumnMatcher(null);
- return cursor;
- }
-
- public IndexImpl getIndex() {
- return _index;
- }
-
- /**
- * @deprecated renamed to {@link #findFirstRowByEntry(Object...)} to be more
- * clear
- */
- @Deprecated
- public boolean findRowByEntry(Object... entryValues)
- throws IOException
- {
- return findFirstRowByEntry(entryValues);
- }
+ public Index getIndex();
/**
* Moves to the first row (as defined by the cursor) where the index entries
* {@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
* @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.
* @param entryValues the column values for the index's columns.
*/
public boolean currentRowMatchesEntry(Object... entryValues)
- throws IOException
- {
- return currentRowMatchesEntryImpl(toRowValues(entryValues));
- }
-
+ throws IOException;
+
/**
* Returns a modifiable Iterator which will iterate through all the rows of
* this table which match the given index entries.
* @throws IllegalStateException if an IOException is thrown by one of the
* operations, the actual exception will be contained within
*/
- public Iterator<Map<String,Object>> entryIterator(Object... entryValues)
- {
- return entryIterator((Collection<String>)null, entryValues);
- }
-
+ public Iterator<Map<String,Object>> entryIterator(Object... entryValues);
+
/**
* Returns a modifiable Iterator which will iterate through all the rows of
* this table which match the given index entries, returning only the given
* operations, the actual exception will be contained within
*/
public Iterator<Map<String,Object>> entryIterator(
- Collection<String> columnNames, Object... entryValues)
- {
- return new EntryIterator(columnNames, toRowValues(entryValues));
- }
-
+ Collection<String> columnNames, Object... entryValues);
+
/**
* Returns an Iterable whose iterator() method returns the result of a call
* to {@link #entryIterator(Object...)}
* @throws IllegalStateException if an IOException is thrown by one of the
* operations, the actual exception will be contained within
*/
- public Iterable<Map<String,Object>> entryIterable(Object... entryValues)
- {
- return entryIterable((Collection<String>)null, entryValues);
- }
-
+ public Iterable<Map<String,Object>> entryIterable(Object... entryValues);
+
/**
* Returns an Iterable whose iterator() method returns the result of a call
* to {@link #entryIterator(Collection,Object...)}
* operations, the actual exception will be contained within
*/
public Iterable<Map<String,Object>> entryIterable(
- final Collection<String> columnNames, final Object... entryValues)
- {
- return new Iterable<Map<String, Object>>() {
- public Iterator<Map<String, Object>> iterator() {
- return new EntryIterator(columnNames, toRowValues(entryValues));
- }
- };
- }
-
- @Override
- protected IndexDirHandler getDirHandler(boolean moveForward) {
- return (moveForward ? _forwardDirHandler : _reverseDirHandler);
- }
-
- @Override
- protected boolean isUpToDate() {
- return(super.isUpToDate() && _entryCursor.isUpToDate());
- }
-
- @Override
- protected void reset(boolean moveForward) {
- _entryCursor.reset(moveForward);
- super.reset(moveForward);
- }
-
- @Override
- protected void restorePositionImpl(Position curPos, Position prevPos)
- throws IOException
- {
- if(!(curPos instanceof IndexPosition) ||
- !(prevPos instanceof IndexPosition)) {
- throw new IllegalArgumentException(
- "Restored positions must be index positions");
- }
- _entryCursor.restorePosition(((IndexPosition)curPos).getEntry(),
- ((IndexPosition)prevPos).getEntry());
- super.restorePositionImpl(curPos, prevPos);
- }
-
- @Override
- protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
- throws IOException
- {
- if(!isBeforeFirst()) {
- // use the default table scan for finding rows mid-cursor
- return super.findNextRowImpl(columnPattern, valuePattern);
- }
-
- // searching for the first match
- Object[] rowValues = _entryCursor.getIndexData().constructIndexRow(
- columnPattern.getName(), valuePattern);
-
- if(rowValues == null) {
- // bummer, use the default table scan
- return super.findNextRowImpl(columnPattern, valuePattern);
- }
-
- // sweet, we can use our index
- if(!findPotentialRow(rowValues, true)) {
- return false;
- }
-
- // either we found a row with the given value, or none exist in the
- // table
- return currentRowMatches(columnPattern, valuePattern);
- }
-
- /**
- * Moves to the first row (as defined by the cursor) where the index entries
- * match the given values. Caller manages save/restore on failure.
- *
- * @param rowValues the column values built from the index column values
- * @param requireMatch whether or not an exact match is found
- * @return {@code true} if a valid row was found with the given values,
- * {@code false} if no row was found
- */
- protected boolean findFirstRowByEntryImpl(Object[] rowValues,
- boolean requireMatch)
- throws IOException
- {
- if(!findPotentialRow(rowValues, requireMatch)) {
- return false;
- } else if(!requireMatch) {
- // nothing more to do, we have moved to the closest row
- return true;
- }
-
- return currentRowMatchesEntryImpl(rowValues);
- }
-
- @Override
- protected boolean findNextRowImpl(Map<String,?> rowPattern)
- throws IOException
- {
- if(!isBeforeFirst()) {
- // use the default table scan for finding rows mid-cursor
- return super.findNextRowImpl(rowPattern);
- }
-
- // searching for the first match
- IndexData indexData = _entryCursor.getIndexData();
- Object[] rowValues = indexData.constructIndexRow(rowPattern);
-
- if(rowValues == null) {
- // bummer, use the default table scan
- return super.findNextRowImpl(rowPattern);
- }
-
- // sweet, we can use our index
- if(!findPotentialRow(rowValues, true)) {
- // at end of index, no potential matches
- return false;
- }
-
- // find actual matching row
- Map<String,?> indexRowPattern = null;
- if(rowPattern.size() == indexData.getColumns().size()) {
- // the rowPattern matches our index columns exactly, so we can
- // streamline our testing below
- indexRowPattern = rowPattern;
- } else {
- // the rowPattern has more columns than just the index, so we need to
- // do more work when testing below
- Map<String,Object> tmpRowPattern = new LinkedHashMap<String,Object>();
- indexRowPattern = tmpRowPattern;
- for(IndexData.ColumnDescriptor idxCol : indexData.getColumns()) {
- tmpRowPattern.put(idxCol.getName(), rowValues[idxCol.getColumnIndex()]);
- }
- }
-
- // there may be multiple columns which fit the pattern subset used by
- // the index, so we need to keep checking until our index values no
- // longer match
- do {
-
- if(!currentRowMatches(indexRowPattern)) {
- // there are no more rows which could possibly match
- break;
- }
-
- // note, if rowPattern == indexRowPattern, no need to do an extra
- // comparison with the current row
- if((rowPattern == indexRowPattern) || currentRowMatches(rowPattern)) {
- // found it!
- return true;
- }
-
- } while(moveToNextRow());
-
- // none of the potential rows matched
- return false;
- }
-
- private boolean currentRowMatchesEntryImpl(Object[] rowValues)
- throws IOException
- {
- if(_indexEntryPattern == null) {
- // init our set of index column names
- _indexEntryPattern = new HashSet<String>();
- for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
- _indexEntryPattern.add(col.getName());
- }
- }
-
- // check the next row to see if it actually matches
- Map<String,Object> row = getCurrentRow(_indexEntryPattern);
-
- for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
- String columnName = col.getName();
- Object patValue = rowValues[col.getColumnIndex()];
- Object rowValue = row.get(columnName);
- if(!_columnMatcher.matches(getTable(), columnName,
- patValue, rowValue)) {
- return false;
- }
- }
-
- return true;
- }
-
- private boolean findPotentialRow(Object[] rowValues, boolean requireMatch)
- throws IOException
- {
- _entryCursor.beforeEntry(rowValues);
- IndexData.Entry startEntry = _entryCursor.getNextEntry();
- if(requireMatch && !startEntry.getRowId().isValid()) {
- // at end of index, no potential matches
- return false;
- }
- // move to position and check it out
- restorePosition(new IndexPosition(startEntry));
- return true;
- }
-
- private Object[] toRowValues(Object[] entryValues)
- {
- return _entryCursor.getIndexData().constructIndexRowFromEntry(entryValues);
- }
-
- @Override
- protected Position findAnotherPosition(RowState rowState, Position curPos,
- boolean moveForward)
- throws IOException
- {
- IndexDirHandler handler = getDirHandler(moveForward);
- IndexPosition endPos = (IndexPosition)handler.getEndPosition();
- IndexData.Entry entry = handler.getAnotherEntry();
- return ((!entry.equals(endPos.getEntry())) ?
- new IndexPosition(entry) : endPos);
- }
-
- @Override
- protected ColumnMatcher getDefaultColumnMatcher() {
- if(getIndex().isUnique()) {
- // text indexes are case-insensitive, therefore we should always use a
- // case-insensitive matcher for unique indexes.
- return CaseInsensitiveColumnMatcher.INSTANCE;
- }
- return SimpleColumnMatcher.INSTANCE;
- }
-
- /**
- * Handles moving the table index cursor in a given direction. Separates
- * cursor logic from value storage.
- */
- private abstract class IndexDirHandler extends DirHandler {
- public abstract IndexData.Entry getAnotherEntry()
- throws IOException;
- }
-
- /**
- * Handles moving the table index cursor forward.
- */
- private final class ForwardIndexDirHandler extends IndexDirHandler {
- @Override
- public Position getBeginningPosition() {
- return getFirstPosition();
- }
- @Override
- public Position getEndPosition() {
- return getLastPosition();
- }
- @Override
- public IndexData.Entry getAnotherEntry() throws IOException {
- return _entryCursor.getNextEntry();
- }
- }
-
- /**
- * Handles moving the table index cursor backward.
- */
- private final class ReverseIndexDirHandler extends IndexDirHandler {
- @Override
- public Position getBeginningPosition() {
- return getLastPosition();
- }
- @Override
- public Position getEndPosition() {
- return getFirstPosition();
- }
- @Override
- public IndexData.Entry getAnotherEntry() throws IOException {
- return _entryCursor.getPreviousEntry();
- }
- }
-
- /**
- * Value object which maintains the current position of an IndexCursor.
- */
- private static final class IndexPosition extends Position
- {
- private final IndexData.Entry _entry;
-
- private IndexPosition(IndexData.Entry entry) {
- _entry = entry;
- }
-
- @Override
- public RowId getRowId() {
- return getEntry().getRowId();
- }
-
- public IndexData.Entry getEntry() {
- return _entry;
- }
-
- @Override
- protected boolean equalsImpl(Object o) {
- return getEntry().equals(((IndexPosition)o).getEntry());
- }
-
- @Override
- public String toString() {
- return "Entry = " + getEntry();
- }
- }
-
- /**
- * Row iterator (by matching entry) for this cursor, modifiable.
- */
- private final class EntryIterator extends BaseIterator
- {
- private final Object[] _rowValues;
-
- private EntryIterator(Collection<String> columnNames, Object[] rowValues)
- {
- super(columnNames);
- _rowValues = rowValues;
- try {
- _hasNext = findFirstRowByEntryImpl(rowValues, true);
- _validRow = _hasNext;
- } catch(IOException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- protected boolean findNext() throws IOException {
- return (moveToNextRow() && currentRowMatchesEntryImpl(_rowValues));
- }
- }
+ Collection<String> columnNames, Object... entryValues);
-
}
--- /dev/null
+/*
+Copyright (c) 2011 James Ahlborn
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import com.healthmarketscience.jackcess.TableImpl.RowState;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Cursor backed by an index with extended traversal options.
+ *
+ * @author James Ahlborn
+ */
+public class IndexCursorImpl extends CursorImpl implements IndexCursor
+{
+ private static final Log LOG = LogFactory.getLog(IndexCursorImpl.class);
+
+ /** IndexDirHandler for forward traversal */
+ private final IndexDirHandler _forwardDirHandler =
+ new ForwardIndexDirHandler();
+ /** IndexDirHandler for backward traversal */
+ private final IndexDirHandler _reverseDirHandler =
+ new ReverseIndexDirHandler();
+ /** logical index which this cursor is using */
+ private final IndexImpl _index;
+ /** Cursor over the entries of the relevant index */
+ private final IndexData.EntryCursor _entryCursor;
+ /** column names for the index entry columns */
+ private Set<String> _indexEntryPattern;
+
+ private IndexCursorImpl(TableImpl table, IndexImpl index,
+ IndexData.EntryCursor entryCursor)
+ throws IOException
+ {
+ super(new IdImpl(table, index), table,
+ new IndexPosition(entryCursor.getFirstEntry()),
+ new IndexPosition(entryCursor.getLastEntry()));
+ _index = index;
+ _index.initialize();
+ _entryCursor = entryCursor;
+ }
+
+ /**
+ * Creates an indexed cursor for the given table.
+ * <p>
+ * Note, index based table traversal may not include all rows, as certain
+ * types of indexes do not include all entries (namely, some indexes ignore
+ * null entries, see {@link Index#shouldIgnoreNulls}).
+ *
+ * @param table the table over which this cursor will traverse
+ * @param index index for the table which will define traversal order as
+ * well as enhance certain lookups
+ */
+ public static IndexCursorImpl createCursor(TableImpl table, IndexImpl index)
+ throws IOException
+ {
+ return createCursor(table, index, null, null);
+ }
+
+ /**
+ * Creates an indexed cursor for the given table, narrowed to the given
+ * range.
+ * <p>
+ * Note, index based table traversal may not include all rows, as certain
+ * types of indexes do not include all entries (namely, some indexes ignore
+ * null entries, see {@link Index#shouldIgnoreNulls}).
+ *
+ * @param table the table over which this cursor will traverse
+ * @param index index for the table which will define traversal order as
+ * well as enhance certain lookups
+ * @param startRow the first row of data for the cursor (inclusive), or
+ * {@code null} for the first entry
+ * @param endRow the last row of data for the cursor (inclusive), or
+ * {@code null} for the last entry
+ */
+ public static IndexCursorImpl createCursor(
+ TableImpl table, IndexImpl index, Object[] startRow, Object[] endRow)
+ throws IOException
+ {
+ return createCursor(table, index, startRow, true, endRow, true);
+ }
+
+ /**
+ * Creates an indexed cursor for the given table, narrowed to the given
+ * range.
+ * <p>
+ * Note, index based table traversal may not include all rows, as certain
+ * types of indexes do not include all entries (namely, some indexes ignore
+ * null entries, see {@link Index#shouldIgnoreNulls}).
+ *
+ * @param table the table over which this cursor will traverse
+ * @param index index for the table which will define traversal order as
+ * well as enhance certain lookups
+ * @param startRow the first row of data for the cursor, or {@code null} for
+ * the first entry
+ * @param startInclusive whether or not startRow is inclusive or exclusive
+ * @param endRow the last row of data for the cursor, or {@code null} for
+ * the last entry
+ * @param endInclusive whether or not endRow is inclusive or exclusive
+ */
+ public static IndexCursorImpl createCursor(TableImpl table, IndexImpl index,
+ Object[] startRow,
+ boolean startInclusive,
+ Object[] endRow,
+ boolean endInclusive)
+ throws IOException
+ {
+ if(table != index.getTable()) {
+ throw new IllegalArgumentException(
+ "Given index is not for given table: " + index + ", " + table);
+ }
+ if(!table.getFormat().INDEXES_SUPPORTED) {
+ throw new IllegalArgumentException(
+ "JetFormat " + table.getFormat() +
+ " does not currently support index lookups");
+ }
+ if(index.getIndexData().isReadOnly()) {
+ throw new IllegalArgumentException(
+ "Given index " + index +
+ " is not usable for indexed lookups because it is read-only");
+ }
+ IndexCursorImpl cursor = new IndexCursorImpl(table, index,
+ index.cursor(startRow, startInclusive,
+ endRow, endInclusive));
+ // init the column matcher appropriately for the index type
+ cursor.setColumnMatcher(null);
+ return cursor;
+ }
+
+ public IndexImpl getIndex() {
+ return _index;
+ }
+
+ public boolean findFirstRowByEntry(Object... entryValues)
+ throws IOException
+ {
+ PositionImpl curPos = _curPos;
+ PositionImpl prevPos = _prevPos;
+ boolean found = false;
+ try {
+ found = findFirstRowByEntryImpl(toRowValues(entryValues), true);
+ return found;
+ } finally {
+ if(!found) {
+ try {
+ restorePosition(curPos, prevPos);
+ } catch(IOException e) {
+ LOG.error("Failed restoring position", e);
+ }
+ }
+ }
+ }
+
+ public void findClosestRowByEntry(Object... entryValues)
+ throws IOException
+ {
+ PositionImpl curPos = _curPos;
+ PositionImpl prevPos = _prevPos;
+ boolean found = false;
+ try {
+ findFirstRowByEntryImpl(toRowValues(entryValues), false);
+ found = true;
+ } finally {
+ if(!found) {
+ try {
+ restorePosition(curPos, prevPos);
+ } catch(IOException e) {
+ LOG.error("Failed restoring position", e);
+ }
+ }
+ }
+ }
+
+ public boolean currentRowMatchesEntry(Object... entryValues)
+ throws IOException
+ {
+ return currentRowMatchesEntryImpl(toRowValues(entryValues));
+ }
+
+ public Iterator<Map<String,Object>> entryIterator(Object... entryValues)
+ {
+ return entryIterator((Collection<String>)null, entryValues);
+ }
+
+ public Iterator<Map<String,Object>> entryIterator(
+ Collection<String> columnNames, Object... entryValues)
+ {
+ return new EntryIterator(columnNames, toRowValues(entryValues));
+ }
+
+ public Iterable<Map<String,Object>> entryIterable(Object... entryValues)
+ {
+ return entryIterable((Collection<String>)null, entryValues);
+ }
+
+ public Iterable<Map<String,Object>> entryIterable(
+ final Collection<String> columnNames, final Object... entryValues)
+ {
+ return new Iterable<Map<String, Object>>() {
+ public Iterator<Map<String, Object>> iterator() {
+ return new EntryIterator(columnNames, toRowValues(entryValues));
+ }
+ };
+ }
+
+ @Override
+ protected IndexDirHandler getDirHandler(boolean moveForward) {
+ return (moveForward ? _forwardDirHandler : _reverseDirHandler);
+ }
+
+ @Override
+ protected boolean isUpToDate() {
+ return(super.isUpToDate() && _entryCursor.isUpToDate());
+ }
+
+ @Override
+ protected void reset(boolean moveForward) {
+ _entryCursor.reset(moveForward);
+ super.reset(moveForward);
+ }
+
+ @Override
+ protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
+ throws IOException
+ {
+ if(!(curPos instanceof IndexPosition) ||
+ !(prevPos instanceof IndexPosition)) {
+ throw new IllegalArgumentException(
+ "Restored positions must be index positions");
+ }
+ _entryCursor.restorePosition(((IndexPosition)curPos).getEntry(),
+ ((IndexPosition)prevPos).getEntry());
+ super.restorePositionImpl(curPos, prevPos);
+ }
+
+ @Override
+ protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
+ throws IOException
+ {
+ if(!isBeforeFirst()) {
+ // use the default table scan for finding rows mid-cursor
+ return super.findNextRowImpl(columnPattern, valuePattern);
+ }
+
+ // searching for the first match
+ Object[] rowValues = _entryCursor.getIndexData().constructIndexRow(
+ columnPattern.getName(), valuePattern);
+
+ if(rowValues == null) {
+ // bummer, use the default table scan
+ return super.findNextRowImpl(columnPattern, valuePattern);
+ }
+
+ // sweet, we can use our index
+ if(!findPotentialRow(rowValues, true)) {
+ return false;
+ }
+
+ // either we found a row with the given value, or none exist in the
+ // table
+ return currentRowMatches(columnPattern, valuePattern);
+ }
+
+ /**
+ * Moves to the first row (as defined by the cursor) where the index entries
+ * match the given values. Caller manages save/restore on failure.
+ *
+ * @param rowValues the column values built from the index column values
+ * @param requireMatch whether or not an exact match is found
+ * @return {@code true} if a valid row was found with the given values,
+ * {@code false} if no row was found
+ */
+ protected boolean findFirstRowByEntryImpl(Object[] rowValues,
+ boolean requireMatch)
+ throws IOException
+ {
+ if(!findPotentialRow(rowValues, requireMatch)) {
+ return false;
+ } else if(!requireMatch) {
+ // nothing more to do, we have moved to the closest row
+ return true;
+ }
+
+ return currentRowMatchesEntryImpl(rowValues);
+ }
+
+ @Override
+ protected boolean findNextRowImpl(Map<String,?> rowPattern)
+ throws IOException
+ {
+ if(!isBeforeFirst()) {
+ // use the default table scan for finding rows mid-cursor
+ return super.findNextRowImpl(rowPattern);
+ }
+
+ // searching for the first match
+ IndexData indexData = _entryCursor.getIndexData();
+ Object[] rowValues = indexData.constructIndexRow(rowPattern);
+
+ if(rowValues == null) {
+ // bummer, use the default table scan
+ return super.findNextRowImpl(rowPattern);
+ }
+
+ // sweet, we can use our index
+ if(!findPotentialRow(rowValues, true)) {
+ // at end of index, no potential matches
+ return false;
+ }
+
+ // find actual matching row
+ Map<String,?> indexRowPattern = null;
+ if(rowPattern.size() == indexData.getColumns().size()) {
+ // the rowPattern matches our index columns exactly, so we can
+ // streamline our testing below
+ indexRowPattern = rowPattern;
+ } else {
+ // the rowPattern has more columns than just the index, so we need to
+ // do more work when testing below
+ Map<String,Object> tmpRowPattern = new LinkedHashMap<String,Object>();
+ indexRowPattern = tmpRowPattern;
+ for(IndexData.ColumnDescriptor idxCol : indexData.getColumns()) {
+ tmpRowPattern.put(idxCol.getName(), rowValues[idxCol.getColumnIndex()]);
+ }
+ }
+
+ // there may be multiple columns which fit the pattern subset used by
+ // the index, so we need to keep checking until our index values no
+ // longer match
+ do {
+
+ if(!currentRowMatches(indexRowPattern)) {
+ // there are no more rows which could possibly match
+ break;
+ }
+
+ // note, if rowPattern == indexRowPattern, no need to do an extra
+ // comparison with the current row
+ if((rowPattern == indexRowPattern) || currentRowMatches(rowPattern)) {
+ // found it!
+ return true;
+ }
+
+ } while(moveToNextRow());
+
+ // none of the potential rows matched
+ return false;
+ }
+
+ private boolean currentRowMatchesEntryImpl(Object[] rowValues)
+ throws IOException
+ {
+ if(_indexEntryPattern == null) {
+ // init our set of index column names
+ _indexEntryPattern = new HashSet<String>();
+ for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
+ _indexEntryPattern.add(col.getName());
+ }
+ }
+
+ // check the next row to see if it actually matches
+ Map<String,Object> row = getCurrentRow(_indexEntryPattern);
+
+ for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
+ String columnName = col.getName();
+ Object patValue = rowValues[col.getColumnIndex()];
+ Object rowValue = row.get(columnName);
+ if(!_columnMatcher.matches(getTable(), columnName,
+ patValue, rowValue)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean findPotentialRow(Object[] rowValues, boolean requireMatch)
+ throws IOException
+ {
+ _entryCursor.beforeEntry(rowValues);
+ IndexData.Entry startEntry = _entryCursor.getNextEntry();
+ if(requireMatch && !startEntry.getRowId().isValid()) {
+ // at end of index, no potential matches
+ return false;
+ }
+ // move to position and check it out
+ restorePosition(new IndexPosition(startEntry));
+ return true;
+ }
+
+ private Object[] toRowValues(Object[] entryValues)
+ {
+ return _entryCursor.getIndexData().constructIndexRowFromEntry(entryValues);
+ }
+
+ @Override
+ protected PositionImpl findAnotherPosition(RowState rowState, PositionImpl curPos,
+ boolean moveForward)
+ throws IOException
+ {
+ IndexDirHandler handler = getDirHandler(moveForward);
+ IndexPosition endPos = (IndexPosition)handler.getEndPosition();
+ IndexData.Entry entry = handler.getAnotherEntry();
+ return ((!entry.equals(endPos.getEntry())) ?
+ new IndexPosition(entry) : endPos);
+ }
+
+ @Override
+ protected ColumnMatcher getDefaultColumnMatcher() {
+ if(getIndex().isUnique()) {
+ // text indexes are case-insensitive, therefore we should always use a
+ // case-insensitive matcher for unique indexes.
+ return CaseInsensitiveColumnMatcher.INSTANCE;
+ }
+ return SimpleColumnMatcher.INSTANCE;
+ }
+
+ /**
+ * Handles moving the table index cursor in a given direction. Separates
+ * cursor logic from value storage.
+ */
+ private abstract class IndexDirHandler extends DirHandler {
+ public abstract IndexData.Entry getAnotherEntry()
+ throws IOException;
+ }
+
+ /**
+ * Handles moving the table index cursor forward.
+ */
+ private final class ForwardIndexDirHandler extends IndexDirHandler {
+ @Override
+ public PositionImpl getBeginningPosition() {
+ return getFirstPosition();
+ }
+ @Override
+ public PositionImpl getEndPosition() {
+ return getLastPosition();
+ }
+ @Override
+ public IndexData.Entry getAnotherEntry() throws IOException {
+ return _entryCursor.getNextEntry();
+ }
+ }
+
+ /**
+ * Handles moving the table index cursor backward.
+ */
+ private final class ReverseIndexDirHandler extends IndexDirHandler {
+ @Override
+ public PositionImpl getBeginningPosition() {
+ return getLastPosition();
+ }
+ @Override
+ public PositionImpl getEndPosition() {
+ return getFirstPosition();
+ }
+ @Override
+ public IndexData.Entry getAnotherEntry() throws IOException {
+ return _entryCursor.getPreviousEntry();
+ }
+ }
+
+ /**
+ * Value object which maintains the current position of an IndexCursor.
+ */
+ private static final class IndexPosition extends PositionImpl
+ {
+ private final IndexData.Entry _entry;
+
+ private IndexPosition(IndexData.Entry entry) {
+ _entry = entry;
+ }
+
+ @Override
+ public RowId getRowId() {
+ return getEntry().getRowId();
+ }
+
+ public IndexData.Entry getEntry() {
+ return _entry;
+ }
+
+ @Override
+ protected boolean equalsImpl(Object o) {
+ return getEntry().equals(((IndexPosition)o).getEntry());
+ }
+
+ @Override
+ public String toString() {
+ return "Entry = " + getEntry();
+ }
+ }
+
+ /**
+ * Row iterator (by matching entry) for this cursor, modifiable.
+ */
+ private final class EntryIterator extends BaseIterator
+ {
+ private final Object[] _rowValues;
+
+ private EntryIterator(Collection<String> columnNames, Object[] rowValues)
+ {
+ super(columnNames);
+ _rowValues = rowValues;
+ try {
+ _hasNext = findFirstRowByEntryImpl(rowValues, true);
+ _validRow = _hasNext;
+ } catch(IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ protected boolean findNext() throws IOException {
+ return (moveToNextRow() && currentRowMatchesEntryImpl(_rowValues));
+ }
+ }
+
+
+}
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();
}
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)
* {@code #getLastEntry} otherwise
*/
public Entry getNextEntry() throws IOException {
- return getAnotherPosition(Cursor.MOVE_FORWARD).getEntry();
+ return getAnotherPosition(CursorImpl.MOVE_FORWARD).getEntry();
}
/**
* {@code #getFirstEntry} otherwise
*/
public Entry getPreviousEntry() throws IOException {
- return getAnotherPosition(Cursor.MOVE_REVERSE).getEntry();
+ return getAnotherPosition(CursorImpl.MOVE_REVERSE).getEntry();
}
/**
{
private final IndexImpl _fromIndex;
private final List<IndexData.ColumnDescriptor> _fromCols;
- private final IndexCursor _toCursor;
+ private final IndexCursorImpl _toCursor;
private final Object[] _entryValues;
- private Joiner(IndexImpl fromIndex, IndexCursor toCursor)
+ private Joiner(IndexImpl fromIndex, IndexCursorImpl toCursor)
{
_fromIndex = fromIndex;
_fromCols = _fromIndex.getColumns();
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);
return getToCursor().getIndex();
}
- public IndexCursor getToCursor() {
+ public IndexCursorImpl getToCursor() {
return _toCursor;
}
/** common cursor for iterating through the table, kept here for historic
reasons */
- private Cursor _cursor;
+ private CursorImpl _cursor;
/**
* Only used by unit tests
return _logicalIndexCount;
}
- private Cursor getInternalCursor() {
+ private CursorImpl getInternalCursor() {
if(_cursor == null) {
- _cursor = Cursor.createCursor(this);
+ _cursor = CursorImpl.createCursor(this);
}
return _cursor;
}
* {@link RowId#LAST_PAGE_NUMBER} otherwise
*/
public int getNextPage() {
- return getAnotherPage(Cursor.MOVE_FORWARD);
+ return getAnotherPage(CursorImpl.MOVE_FORWARD);
}
/**
* {@link RowId#FIRST_PAGE_NUMBER} otherwise
*/
public int getPreviousPage() {
- return getAnotherPage(Cursor.MOVE_REVERSE);
+ return getAnotherPage(CursorImpl.MOVE_REVERSE);
}
/**
* page in the map
*/
public void beforeFirst() {
- reset(Cursor.MOVE_FORWARD);
+ reset(CursorImpl.MOVE_FORWARD);
}
/**
* last page in the map
*/
public void afterLast() {
- reset(Cursor.MOVE_REVERSE);
+ reset(CursorImpl.MOVE_REVERSE);
}
/**
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;
private final List<ColumnImpl> _typeCols;
private final ColumnImpl _pkCol;
private final ColumnImpl _complexValFkCol;
- private IndexCursor _pkCursor;
- private IndexCursor _complexValIdCursor;
+ private IndexCursorImpl _pkCursor;
+ private IndexCursorImpl _complexValIdCursor;
protected ComplexColumnInfo(ColumnImpl column, int complexTypeId,
TableImpl typeObjTable, TableImpl flatTable)
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(