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