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