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