View Javadoc
1   /*
2   Copyright (c) 2007 Health Market Science, Inc.
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl;
18  
19  import java.io.IOException;
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.Iterator;
23  import java.util.Map;
24  import java.util.NoSuchElementException;
25  import java.util.function.Predicate;
26  
27  import com.healthmarketscience.jackcess.Column;
28  import com.healthmarketscience.jackcess.Cursor;
29  import com.healthmarketscience.jackcess.CursorBuilder;
30  import com.healthmarketscience.jackcess.Row;
31  import com.healthmarketscience.jackcess.RowId;
32  import com.healthmarketscience.jackcess.RuntimeIOException;
33  import com.healthmarketscience.jackcess.impl.TableImpl.RowState;
34  import com.healthmarketscience.jackcess.util.ColumnMatcher;
35  import com.healthmarketscience.jackcess.util.ErrorHandler;
36  import com.healthmarketscience.jackcess.util.IterableBuilder;
37  import com.healthmarketscience.jackcess.util.SimpleColumnMatcher;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  
41  /**
42   * Manages iteration for a Table.  Different cursors provide different methods
43   * of traversing a table.  Cursors should be fairly robust in the face of
44   * table modification during traversal (although depending on how the table is
45   * traversed, row updates may or may not be seen).  Multiple cursors may
46   * traverse the same table simultaneously.
47   * <p>
48   * The Cursor provides a variety of static utility methods to construct
49   * cursors with given characteristics or easily search for specific values.
50   * For even friendlier and more flexible construction, see
51   * {@link CursorBuilder}.
52   * <p>
53   * Is not thread-safe.
54   *
55   * @author James Ahlborn
56   */
57  public abstract class CursorImpl implements Cursor
58  {
59    private static final Log LOG = LogFactory.getLog(CursorImpl.class);
60  
61    /** boolean value indicating forward movement */
62    public static final boolean MOVE_FORWARD = true;
63    /** boolean value indicating reverse movement */
64    public static final boolean MOVE_REVERSE = false;
65  
66    /** identifier for this cursor */
67    private final IdImpl _id;
68    /** owning table */
69    private final TableImpl _table;
70    /** State used for reading the table rows */
71    private final RowState _rowState;
72    /** the first (exclusive) row id for this cursor */
73    private final PositionImpl _firstPos;
74    /** the last (exclusive) row id for this cursor */
75    private final PositionImpl _lastPos;
76    /** the previous row */
77    protected PositionImpl _prevPos;
78    /** the current row */
79    protected PositionImpl _curPos;
80    /** ColumnMatcher to be used when matching column values */
81    protected ColumnMatcher _columnMatcher = SimpleColumnMatcher.INSTANCE;
82  
83    protected CursorImpl(IdImpl id, TableImpl table, PositionImpl firstPos,
84                         PositionImpl lastPos) {
85      _id = id;
86      _table = table;
87      _rowState = _table.createRowState();
88      _firstPos = firstPos;
89      _lastPos = lastPos;
90      _curPos = firstPos;
91      _prevPos = firstPos;
92    }
93  
94    /**
95     * Creates a normal, un-indexed cursor for the given table.
96     * @param table the table over which this cursor will traverse
97     */
98    public static CursorImpl createCursor(TableImpl table) {
99      return new TableScanCursor(table);
100   }
101 
102   public RowState getRowState() {
103     return _rowState;
104   }
105 
106   @Override
107   public IdImpl getId() {
108     return _id;
109   }
110 
111   @Override
112   public TableImpl getTable() {
113     return _table;
114   }
115 
116   public JetFormat getFormat() {
117     return getTable().getFormat();
118   }
119 
120   public PageChannel getPageChannel() {
121     return getTable().getPageChannel();
122   }
123 
124   @Override
125   public ErrorHandler getErrorHandler() {
126     return _rowState.getErrorHandler();
127   }
128 
129   @Override
130   public void setErrorHandler(ErrorHandler newErrorHandler) {
131     _rowState.setErrorHandler(newErrorHandler);
132   }
133 
134   @Override
135   public ColumnMatcher getColumnMatcher() {
136     return _columnMatcher;
137   }
138 
139   @Override
140   public void setColumnMatcher(ColumnMatcher columnMatcher) {
141     if(columnMatcher == null) {
142       columnMatcher = getDefaultColumnMatcher();
143     }
144     _columnMatcher = columnMatcher;
145   }
146 
147   /**
148    * Returns the default ColumnMatcher for this Cursor.
149    */
150   protected ColumnMatcher getDefaultColumnMatcher() {
151     return SimpleColumnMatcher.INSTANCE;
152   }
153 
154   @Override
155   public SavepointImpl getSavepoint() {
156     return new SavepointImpl(_id, _curPos, _prevPos);
157   }
158 
159   @Override
160   public void restoreSavepoint(Savepoint savepoint)
161     throws IOException
162   {
163     restoreSavepoint((SavepointImpl)savepoint);
164   }
165 
166   public void restoreSavepoint(SavepointImpl savepoint)
167     throws IOException
168   {
169     if(!_id.equals(savepoint.getCursorId())) {
170       throw new IllegalArgumentException(
171           "Savepoint " + savepoint + " is not valid for this cursor with id "
172           + _id);
173     }
174     restorePosition(savepoint.getCurrentPosition(),
175                     savepoint.getPreviousPosition());
176   }
177 
178   /**
179    * Returns the first row id (exclusive) as defined by this cursor.
180    */
181   protected PositionImpl getFirstPosition() {
182     return _firstPos;
183   }
184 
185   /**
186    * Returns the last row id (exclusive) as defined by this cursor.
187    */
188   protected PositionImpl getLastPosition() {
189     return _lastPos;
190   }
191 
192   @Override
193   public void reset() {
194     beforeFirst();
195   }
196 
197   @Override
198   public void beforeFirst() {
199     reset(MOVE_FORWARD);
200   }
201 
202   @Override
203   public void afterLast() {
204     reset(MOVE_REVERSE);
205   }
206 
207   @Override
208   public boolean isBeforeFirst() throws IOException {
209     return isAtBeginning(MOVE_FORWARD);
210   }
211 
212   @Override
213   public boolean isAfterLast() throws IOException {
214     return isAtBeginning(MOVE_REVERSE);
215   }
216 
217   protected boolean isAtBeginning(boolean moveForward) throws IOException {
218     if(getDirHandler(moveForward).getBeginningPosition().equals(_curPos)) {
219       return !recheckPosition(!moveForward);
220     }
221     return false;
222   }
223 
224   @Override
225   public boolean isCurrentRowDeleted() throws IOException
226   {
227     // we need to ensure that the "deleted" flag has been read for this row
228     // (or re-read if the table has been recently modified)
229     TableImpl.positionAtRowData(_rowState, _curPos.getRowId());
230     return _rowState.isDeleted();
231   }
232 
233   /**
234    * Resets this cursor for traversing the given direction.
235    */
236   protected void reset(boolean moveForward) {
237     _curPos = getDirHandler(moveForward).getBeginningPosition();
238     _prevPos = _curPos;
239     _rowState.reset();
240   }
241 
242   @Override
243   public Iterator<Row> iterator() {
244     return new RowIterator(null, true, MOVE_FORWARD);
245   }
246 
247   @Override
248   public IterableBuilder newIterable() {
249     return new IterableBuilder(this);
250   }
251 
252   public Iterator<Row> iterator(IterableBuilder iterBuilder) {
253 
254     switch(iterBuilder.getType()) {
255     case SIMPLE:
256       return new RowIterator(iterBuilder.getColumnNames(),
257                              iterBuilder.isReset(), iterBuilder.isForward());
258     case COLUMN_MATCH: {
259       @SuppressWarnings("unchecked")
260       Map.Entry<Column,Object> matchPattern = (Map.Entry<Column,Object>)
261         iterBuilder.getMatchPattern();
262       return new ColumnMatchIterator(
263           iterBuilder.getColumnNames(), (ColumnImpl)matchPattern.getKey(),
264           matchPattern.getValue(), iterBuilder.isReset(),
265           iterBuilder.isForward(), iterBuilder.getColumnMatcher());
266     }
267     case ROW_MATCH: {
268       @SuppressWarnings("unchecked")
269       Map<String,?> matchPattern = (Map<String,?>)
270         iterBuilder.getMatchPattern();
271       return new RowMatchIterator(
272           iterBuilder.getColumnNames(), matchPattern,iterBuilder.isReset(),
273           iterBuilder.isForward(), iterBuilder.getColumnMatcher());
274     }
275     default:
276       throw new RuntimeException("unknown match type " + iterBuilder.getType());
277     }
278   }
279 
280   @Override
281   public void deleteCurrentRow() throws IOException {
282     _table.deleteRow(_rowState, _curPos.getRowId());
283   }
284 
285   @Override
286   public Object[] updateCurrentRow(Object... row) throws IOException {
287     return _table.updateRow(_rowState, _curPos.getRowId(), row);
288   }
289 
290   @Override
291   public <M extends Map<String,Object>> M updateCurrentRowFromMap(M row)
292     throws IOException
293   {
294     return _table.updateRowFromMap(_rowState, _curPos.getRowId(), row);
295   }
296 
297   @Override
298   public Row getNextRow() throws IOException {
299     return getNextRow(null);
300   }
301 
302   @Override
303   public Row getNextRow(Collection<String> columnNames)
304     throws IOException
305   {
306     return getAnotherRow(columnNames, MOVE_FORWARD);
307   }
308 
309   @Override
310   public Row getPreviousRow() throws IOException {
311     return getPreviousRow(null);
312   }
313 
314   @Override
315   public Row getPreviousRow(Collection<String> columnNames)
316     throws IOException
317   {
318     return getAnotherRow(columnNames, MOVE_REVERSE);
319   }
320 
321 
322   /**
323    * Moves to another row in the table based on the given direction and
324    * returns it.
325    * @param columnNames Only column names in this collection will be returned
326    * @return another row in this table (Column name -&gt; Column value), where
327    *         "next" may be backwards if moveForward is {@code false}, or
328    *         {@code null} if there is not another row in the given direction.
329    */
330   private Row getAnotherRow(Collection<String> columnNames,
331                             boolean moveForward)
332     throws IOException
333   {
334     if(moveToAnotherRow(moveForward)) {
335       return getCurrentRow(columnNames);
336     }
337     return null;
338   }
339 
340   @Override
341   public boolean moveToNextRow() throws IOException
342   {
343     return moveToAnotherRow(MOVE_FORWARD);
344   }
345 
346   @Override
347   public boolean moveToPreviousRow() throws IOException
348   {
349     return moveToAnotherRow(MOVE_REVERSE);
350   }
351 
352   /**
353    * Moves to another row in the given direction as defined by this cursor.
354    * @return {@code true} if another valid row was found in the given
355    *         direction, {@code false} otherwise
356    */
357   protected boolean moveToAnotherRow(boolean moveForward)
358     throws IOException
359   {
360     if(_curPos.equals(getDirHandler(moveForward).getEndPosition())) {
361       // already at end, make sure nothing has changed
362       return recheckPosition(moveForward);
363     }
364 
365     return moveToAnotherRowImpl(moveForward);
366   }
367 
368   /**
369    * Restores a current position for the cursor (current position becomes
370    * previous position).
371    */
372   protected void restorePosition(PositionImpl curPos)
373     throws IOException
374   {
375     restorePosition(curPos, _curPos);
376   }
377 
378   /**
379    * Restores a current and previous position for the cursor if the given
380    * positions are different from the current positions.
381    */
382   protected final void restorePosition(PositionImpl curPos,
383                                        PositionImpl prevPos)
384     throws IOException
385   {
386     if(!curPos.equals(_curPos) || !prevPos.equals(_prevPos)) {
387       restorePositionImpl(curPos, prevPos);
388     }
389   }
390 
391   /**
392    * Restores a current and previous position for the cursor.
393    */
394   protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
395     throws IOException
396   {
397     // make the current position previous, and the new position current
398     _prevPos = _curPos;
399     _curPos = curPos;
400     _rowState.reset();
401   }
402 
403   /**
404    * Rechecks the current position if the underlying data structures have been
405    * modified.
406    * @return {@code true} if the cursor ended up in a new position,
407    *         {@code false} otherwise.
408    */
409   private boolean recheckPosition(boolean moveForward)
410     throws IOException
411   {
412     if(isUpToDate()) {
413       // nothing has changed
414       return false;
415     }
416 
417     // move the cursor back to the previous position
418     restorePosition(_prevPos);
419     return moveToAnotherRowImpl(moveForward);
420   }
421 
422   /**
423    * Does the grunt work of moving the cursor to another position in the given
424    * direction.
425    */
426   private boolean moveToAnotherRowImpl(boolean moveForward)
427     throws IOException
428   {
429     _rowState.reset();
430     _prevPos = _curPos;
431     _curPos = findAnotherPosition(_rowState, _curPos, moveForward);
432     TableImpl.positionAtRowHeader(_rowState, _curPos.getRowId());
433     return(!_curPos.equals(getDirHandler(moveForward).getEndPosition()));
434   }
435 
436   @Override
437   public boolean findRow(RowId rowId) throws IOException
438   {
439     RowIdImpl../../com/healthmarketscience/jackcess/impl/RowIdImpl.html#RowIdImpl">RowIdImpl rowIdImpl = (RowIdImpl)rowId;
440     PositionImpl curPos = _curPos;
441     PositionImpl prevPos = _prevPos;
442     boolean found = false;
443     try {
444       reset(MOVE_FORWARD);
445       if(TableImpl.positionAtRowHeader(_rowState, rowIdImpl) == null) {
446         return false;
447       }
448       restorePosition(getRowPosition(rowIdImpl));
449       if(!isCurrentRowValid()) {
450         return false;
451       }
452       found = true;
453       return true;
454     } finally {
455       if(!found) {
456         try {
457           restorePosition(curPos, prevPos);
458         } catch(IOException e) {
459           LOG.error("Failed restoring position", e);
460         }
461       }
462     }
463   }
464 
465   @Override
466   public boolean findFirstRow(Column columnPattern, Object valuePattern)
467     throws IOException
468   {
469     return findFirstRow((ColumnImpl)columnPattern, valuePattern);
470   }
471 
472   public boolean findFirstRow(ColumnImpl columnPattern, Object valuePattern)
473     throws IOException
474   {
475     return findAnotherRow(columnPattern, valuePattern, true, MOVE_FORWARD,
476                           _columnMatcher,
477                           prepareSearchInfo(columnPattern, valuePattern));
478   }
479 
480   @Override
481   public boolean findNextRow(Column columnPattern, Object valuePattern)
482     throws IOException
483   {
484     return findNextRow((ColumnImpl)columnPattern, valuePattern);
485   }
486 
487   public boolean findNextRow(ColumnImpl columnPattern, Object valuePattern)
488     throws IOException
489   {
490     return findAnotherRow(columnPattern, valuePattern, false, MOVE_FORWARD,
491                           _columnMatcher,
492                           prepareSearchInfo(columnPattern, valuePattern));
493   }
494 
495   protected boolean findAnotherRow(ColumnImpl columnPattern, Object valuePattern,
496                                    boolean reset, boolean moveForward,
497                                    ColumnMatcher columnMatcher, Object searchInfo)
498     throws IOException
499   {
500     PositionImpl curPos = _curPos;
501     PositionImpl prevPos = _prevPos;
502     boolean found = false;
503     try {
504       if(reset) {
505         reset(moveForward);
506       }
507       found = findAnotherRowImpl(columnPattern, valuePattern, moveForward,
508                                  columnMatcher, searchInfo);
509       return found;
510     } finally {
511       if(!found) {
512         try {
513           restorePosition(curPos, prevPos);
514         } catch(IOException e) {
515           LOG.error("Failed restoring position", e);
516         }
517       }
518     }
519   }
520 
521   @Override
522   public boolean findFirstRow(Map<String,?> rowPattern) throws IOException
523   {
524     return findAnotherRow(rowPattern, true, MOVE_FORWARD, _columnMatcher,
525                           prepareSearchInfo(rowPattern));
526   }
527 
528   @Override
529   public boolean findNextRow(Map<String,?> rowPattern)
530     throws IOException
531   {
532     return findAnotherRow(rowPattern, false, MOVE_FORWARD, _columnMatcher,
533                           prepareSearchInfo(rowPattern));
534   }
535 
536   protected boolean findAnotherRow(Map<String,?> rowPattern, boolean reset,
537                                    boolean moveForward,
538                                    ColumnMatcher columnMatcher, Object searchInfo)
539     throws IOException
540   {
541     PositionImpl curPos = _curPos;
542     PositionImpl prevPos = _prevPos;
543     boolean found = false;
544     try {
545       if(reset) {
546         reset(moveForward);
547       }
548       found = findAnotherRowImpl(rowPattern, moveForward, columnMatcher,
549                                  searchInfo);
550       return found;
551     } finally {
552       if(!found) {
553         try {
554           restorePosition(curPos, prevPos);
555         } catch(IOException e) {
556           LOG.error("Failed restoring position", e);
557         }
558       }
559     }
560   }
561 
562   @Override
563   public boolean currentRowMatches(Column columnPattern, Object valuePattern)
564     throws IOException
565   {
566     return currentRowMatches((ColumnImpl)columnPattern, valuePattern);
567   }
568 
569   public boolean currentRowMatches(ColumnImpl columnPattern, Object valuePattern)
570     throws IOException
571   {
572     return currentRowMatchesImpl(columnPattern, valuePattern, _columnMatcher);
573   }
574 
575   protected boolean currentRowMatchesImpl(ColumnImpl columnPattern,
576                                           Object valuePattern,
577                                           ColumnMatcher columnMatcher)
578     throws IOException
579   {
580     return currentRowMatchesPattern(
581         columnPattern.getName(), valuePattern, columnMatcher,
582         getCurrentRowValue(columnPattern));
583   }
584 
585   @Override
586   public boolean currentRowMatches(Map<String,?> rowPattern)
587     throws IOException
588   {
589     return currentRowMatchesImpl(rowPattern, _columnMatcher);
590   }
591 
592   protected boolean currentRowMatchesImpl(Map<String,?> rowPattern,
593                                           ColumnMatcher columnMatcher)
594     throws IOException
595   {
596     Row row = getCurrentRow(rowPattern.keySet());
597 
598     if(rowPattern.size() != row.size()) {
599       return false;
600     }
601 
602     for(Map.Entry<String,Object> e : row.entrySet()) {
603       String columnName = e.getKey();
604       if(!currentRowMatchesPattern(columnName, rowPattern.get(columnName),
605                                    columnMatcher, e.getValue())) {
606         return false;
607       }
608     }
609 
610     return true;
611   }
612 
613   @SuppressWarnings("unchecked")
614   protected final boolean currentRowMatchesPattern(
615       String columnPattern, Object valuePattern,
616       ColumnMatcher columnMatcher, Object rowValue) {
617     // if the value pattern is a Predicate use that to test the value
618     if(valuePattern instanceof Predicate<?>) {
619       return ((Predicate<Object>)valuePattern).test(rowValue);
620     }
621     // otherwise, use the configured ColumnMatcher
622     return columnMatcher.matches(getTable(), columnPattern, valuePattern,
623                                  rowValue);
624   }
625 
626   /**
627    * Moves to the next row (as defined by the cursor) where the given column
628    * has the given value.  Caller manages save/restore on failure.
629    * <p>
630    * Default implementation scans the table from beginning to end.
631    *
632    * @param columnPattern column from the table for this cursor which is being
633    *                      matched by the valuePattern
634    * @param valuePattern value which is equal to the corresponding value in
635    *                     the matched row
636    * @return {@code true} if a valid row was found with the given value,
637    *         {@code false} if no row was found
638    */
639   protected boolean findAnotherRowImpl(
640       ColumnImpl columnPattern, Object valuePattern, boolean moveForward,
641       ColumnMatcher columnMatcher, Object searchInfo)
642     throws IOException
643   {
644     while(moveToAnotherRow(moveForward)) {
645       if(currentRowMatchesImpl(columnPattern, valuePattern, columnMatcher)) {
646         return true;
647       }
648       if(!keepSearching(columnMatcher, searchInfo)) {
649         break;
650       }
651     }
652     return false;
653   }
654 
655   /**
656    * Moves to the next row (as defined by the cursor) where the given columns
657    * have the given values.  Caller manages save/restore on failure.
658    * <p>
659    * Default implementation scans the table from beginning to end.
660    *
661    * @param rowPattern column names and values which must be equal to the
662    *                   corresponding values in the matched row
663    * @return {@code true} if a valid row was found with the given values,
664    *         {@code false} if no row was found
665    */
666   protected boolean findAnotherRowImpl(Map<String,?> rowPattern,
667                                        boolean moveForward,
668                                        ColumnMatcher columnMatcher,
669                                        Object searchInfo)
670     throws IOException
671   {
672     while(moveToAnotherRow(moveForward)) {
673       if(currentRowMatchesImpl(rowPattern, columnMatcher)) {
674         return true;
675       }
676       if(!keepSearching(columnMatcher, searchInfo)) {
677         break;
678       }
679     }
680     return false;
681   }
682 
683   /**
684    * Called before a search commences to allow for search specific data to be
685    * generated (which is cached for re-use by the iterators).
686    */
687   protected Object prepareSearchInfo(ColumnImpl columnPattern, Object valuePattern)
688   {
689     return null;
690   }
691 
692   /**
693    * Called before a search commences to allow for search specific data to be
694    * generated (which is cached for re-use by the iterators).
695    */
696   protected Object prepareSearchInfo(Map<String,?> rowPattern)
697   {
698     return null;
699   }
700 
701   /**
702    * Called by findAnotherRowImpl to determine if the search should continue
703    * after finding a row which does not match the current pattern.
704    */
705   protected boolean keepSearching(ColumnMatcher columnMatcher,
706                                   Object searchInfo)
707     throws IOException
708   {
709     return true;
710   }
711 
712   @Override
713   public int moveNextRows(int numRows) throws IOException
714   {
715     return moveSomeRows(numRows, MOVE_FORWARD);
716   }
717 
718   @Override
719   public int movePreviousRows(int numRows) throws IOException
720   {
721     return moveSomeRows(numRows, MOVE_REVERSE);
722   }
723 
724   /**
725    * Moves as many rows as possible in the given direction up to the given
726    * number of rows.
727    * @return the number of rows moved.
728    */
729   private int moveSomeRows(int numRows, boolean moveForward)
730     throws IOException
731   {
732     int numMovedRows = 0;
733     while((numMovedRows < numRows) && moveToAnotherRow(moveForward)) {
734       ++numMovedRows;
735     }
736     return numMovedRows;
737   }
738 
739   @Override
740   public Row getCurrentRow() throws IOException
741   {
742     return getCurrentRow(null);
743   }
744 
745   @Override
746   public Row getCurrentRow(Collection<String> columnNames)
747     throws IOException
748   {
749     return _table.getRow(_rowState, _curPos.getRowId(), columnNames);
750   }
751 
752   @Override
753   public Object getCurrentRowValue(Column column)
754     throws IOException
755   {
756     return getCurrentRowValue((ColumnImpl)column);
757   }
758 
759   public Object getCurrentRowValue(ColumnImpl column)
760     throws IOException
761   {
762     return _table.getRowValue(_rowState, _curPos.getRowId(), column);
763   }
764 
765   @Override
766   public void setCurrentRowValue(Column column, Object value)
767     throws IOException
768   {
769     setCurrentRowValue((ColumnImpl)column, value);
770   }
771 
772   public void setCurrentRowValue(ColumnImpl column, Object value)
773     throws IOException
774   {
775     Object[] row = new Object[_table.getColumnCount()];
776     Arrays.fill(row, Column.KEEP_VALUE);
777     column.setRowValue(row, value);
778     _table.updateRow(_rowState, _curPos.getRowId(), row);
779   }
780 
781   /**
782    * Returns {@code true} if this cursor is up-to-date with respect to the
783    * relevant table and related table objects, {@code false} otherwise.
784    */
785   protected boolean isUpToDate() {
786     return _rowState.isUpToDate();
787   }
788 
789   /**
790    * Returns {@code true} of the current row is valid, {@code false} otherwise.
791    */
792   protected boolean isCurrentRowValid() throws IOException {
793     return(_curPos.getRowId().isValid() && !isCurrentRowDeleted() &&
794            !isBeforeFirst() && !isAfterLast());
795   }
796 
797   @Override
798   public String toString() {
799     return getClass().getSimpleName() + " CurPosition " + _curPos +
800       ", PrevPosition " + _prevPos;
801   }
802 
803   /**
804    * Returns the appropriate position information for the given row (which is
805    * the current row and is valid).
806    */
807   protected abstract PositionImpl getRowPosition(RowIdImpl rowId)
808     throws IOException;
809 
810   /**
811    * Finds the next non-deleted row after the given row (as defined by this
812    * cursor) and returns the id of the row, where "next" may be backwards if
813    * moveForward is {@code false}.  If there are no more rows, the returned
814    * rowId should equal the value returned by {@link #getLastPosition} if
815    * moving forward and {@link #getFirstPosition} if moving backward.
816    */
817   protected abstract PositionImpl findAnotherPosition(RowState rowState,
818                                                       PositionImpl curPos,
819                                                       boolean moveForward)
820     throws IOException;
821 
822   /**
823    * Returns the DirHandler for the given movement direction.
824    */
825   protected abstract DirHandler getDirHandler(boolean moveForward);
826 
827 
828   /**
829    * Base implementation of iterator for this cursor, modifiable.
830    */
831   protected abstract class BaseIterator implements Iterator<Row>
832   {
833     protected final Collection<String> _columnNames;
834     protected final boolean _moveForward;
835     protected final ColumnMatcher _colMatcher;
836     protected Boolean _hasNext;
837     protected boolean _validRow;
838 
839     protected BaseIterator(Collection<String> columnNames,
840                            boolean reset, boolean moveForward,
841                            ColumnMatcher columnMatcher)
842     {
843       _columnNames = columnNames;
844       _moveForward = moveForward;
845       _colMatcher = ((columnMatcher != null) ? columnMatcher : _columnMatcher);
846       try {
847         if(reset) {
848           reset(_moveForward);
849         } else if(isCurrentRowValid()) {
850           _hasNext = _validRow = true;
851         }
852       } catch(IOException e) {
853         throw new RuntimeIOException(e);
854       }
855     }
856 
857     @Override
858     public boolean hasNext() {
859       if(_hasNext == null) {
860         try {
861           _hasNext = findNext();
862           _validRow = _hasNext;
863         } catch(IOException e) {
864           throw new RuntimeIOException(e);
865         }
866       }
867       return _hasNext;
868     }
869 
870     @Override
871     public Row next() {
872       if(!hasNext()) {
873         throw new NoSuchElementException();
874       }
875       try {
876         Row rtn = getCurrentRow(_columnNames);
877         _hasNext = null;
878         return rtn;
879       } catch(IOException e) {
880         throw new RuntimeIOException(e);
881       }
882     }
883 
884     @Override
885     public void remove() {
886       if(_validRow) {
887         try {
888           deleteCurrentRow();
889           _validRow = false;
890         } catch(IOException e) {
891           throw new RuntimeIOException(e);
892         }
893       } else {
894         throw new IllegalStateException("Not at valid row");
895       }
896     }
897 
898     protected abstract boolean findNext() throws IOException;
899   }
900 
901 
902   /**
903    * Row iterator for this cursor, modifiable.
904    */
905   private final class RowIterator extends BaseIterator
906   {
907     private RowIterator(Collection<String> columnNames, boolean reset,
908                         boolean moveForward)
909     {
910       super(columnNames, reset, moveForward, null);
911     }
912 
913     @Override
914     protected boolean findNext() throws IOException {
915       return moveToAnotherRow(_moveForward);
916     }
917   }
918 
919 
920   /**
921    * Row iterator for this cursor, modifiable.
922    */
923   private final class ColumnMatchIterator extends BaseIterator
924   {
925     private final ColumnImpl _columnPattern;
926     private final Object _valuePattern;
927     private final Object _searchInfo;
928 
929     private ColumnMatchIterator(Collection<String> columnNames,
930                                 ColumnImpl columnPattern, Object valuePattern,
931                                 boolean reset, boolean moveForward,
932                                 ColumnMatcher columnMatcher)
933     {
934       super(columnNames, reset, moveForward, columnMatcher);
935       _columnPattern = columnPattern;
936       _valuePattern = valuePattern;
937       _searchInfo = prepareSearchInfo(columnPattern, valuePattern);
938     }
939 
940     @Override
941     protected boolean findNext() throws IOException {
942       return findAnotherRow(_columnPattern, _valuePattern, false, _moveForward,
943                             _colMatcher, _searchInfo);
944     }
945   }
946 
947 
948   /**
949    * Row iterator for this cursor, modifiable.
950    */
951   private final class RowMatchIterator extends BaseIterator
952   {
953     private final Map<String,?> _rowPattern;
954     private final Object _searchInfo;
955 
956     private RowMatchIterator(Collection<String> columnNames,
957                              Map<String,?> rowPattern,
958                              boolean reset, boolean moveForward,
959                              ColumnMatcher columnMatcher)
960     {
961       super(columnNames, reset, moveForward, columnMatcher);
962       _rowPattern = rowPattern;
963       _searchInfo = prepareSearchInfo(rowPattern);
964     }
965 
966     @Override
967     protected boolean findNext() throws IOException {
968       return findAnotherRow(_rowPattern, false, _moveForward, _colMatcher,
969                             _searchInfo);
970     }
971   }
972 
973 
974   /**
975    * Handles moving the cursor in a given direction.  Separates cursor
976    * logic from value storage.
977    */
978   protected abstract class DirHandler
979   {
980     public abstract PositionImpl getBeginningPosition();
981     public abstract PositionImpl getEndPosition();
982   }
983 
984 
985   /**
986    * Identifier for a cursor.  Will be equal to any other cursor of the same
987    * type for the same table.  Primarily used to check the validity of a
988    * Savepoint.
989    */
990   protected static final class IdImpl implements Id
991   {
992     private final int _tablePageNumber;
993     private final int _indexNumber;
994 
995     protected IdImpl(TableImpl table, IndexImpl index) {
996       _tablePageNumber = table.getTableDefPageNumber();
997       _indexNumber = ((index != null) ? index.getIndexNumber() : -1);
998     }
999 
1000     @Override
1001     public int hashCode() {
1002       return _tablePageNumber;
1003     }
1004 
1005     @Override
1006     public boolean equals(Object o) {
1007       return((this == o) ||
1008              ((o != null) && (getClass() == o.getClass()) &&
1009               (_tablePageNumber == ((IdImpl)o)._tablePageNumber) &&
1010               (_indexNumber == ((IdImpl)o)._indexNumber)));
1011     }
1012 
1013     @Override
1014     public String toString() {
1015       return getClass().getSimpleName() + " " + _tablePageNumber + ":" + _indexNumber;
1016     }
1017   }
1018 
1019   /**
1020    * Value object which maintains the current position of the cursor.
1021    */
1022   protected static abstract class PositionImpl implements Position
1023   {
1024     protected PositionImpl() {
1025     }
1026 
1027     @Override
1028     public final int hashCode() {
1029       return getRowId().hashCode();
1030     }
1031 
1032     @Override
1033     public final boolean equals(Object o) {
1034       return((this == o) ||
1035              ((o != null) && (getClass() == o.getClass()) && equalsImpl(o)));
1036     }
1037 
1038     /**
1039      * Returns the unique RowId of the position of the cursor.
1040      */
1041     @Override
1042     public abstract RowIdImpl getRowId();
1043 
1044     /**
1045      * Returns {@code true} if the subclass specific info in a Position is
1046      * equal, {@code false} otherwise.
1047      * @param o object being tested for equality, guaranteed to be the same
1048      *          class as this object
1049      */
1050     protected abstract boolean equalsImpl(Object o);
1051   }
1052 
1053   /**
1054    * Value object which represents a complete save state of the cursor.
1055    */
1056   protected static final class SavepointImpl implements Savepoint
1057   {
1058     private final IdImpl _cursorId;
1059     private final PositionImpl _curPos;
1060     private final PositionImpl _prevPos;
1061 
1062     private SavepointImpl(IdImpl cursorId, PositionImpl curPos,
1063                           PositionImpl prevPos) {
1064       _cursorId = cursorId;
1065       _curPos = curPos;
1066       _prevPos = prevPos;
1067     }
1068 
1069     @Override
1070     public IdImpl getCursorId() {
1071       return _cursorId;
1072     }
1073 
1074     @Override
1075     public PositionImpl getCurrentPosition() {
1076       return _curPos;
1077     }
1078 
1079     private PositionImpl getPreviousPosition() {
1080       return _prevPos;
1081     }
1082 
1083     @Override
1084     public String toString() {
1085       return getClass().getSimpleName() + " " + _cursorId + " CurPosition " +
1086         _curPos + ", PrevPosition " + _prevPos;
1087     }
1088   }
1089 
1090 }