View Javadoc

1   /*
2   Copyright (c) 2007 Health Market Science, Inc.
3   
4   This library is free software; you can redistribute it and/or
5   modify it under the terms of the GNU Lesser General Public
6   License as published by the Free Software Foundation; either
7   version 2.1 of the License, or (at your option) any later version.
8   
9   This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  Lesser General Public License for more details.
13  
14  You should have received a copy of the GNU Lesser General Public
15  License along with this library; if not, write to the Free Software
16  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
17  USA
18  
19  You can contact Health Market Science at info@healthmarketscience.com
20  or at the following address:
21  
22  Health Market Science
23  2700 Horizon Drive
24  Suite 200
25  King of Prussia, PA 19406
26  */
27  
28  package com.healthmarketscience.jackcess;
29  
30  import java.io.IOException;
31  import java.util.Collection;
32  import java.util.Iterator;
33  import java.util.LinkedHashMap;
34  import java.util.Map;
35  import java.util.NoSuchElementException;
36  
37  import com.healthmarketscience.jackcess.Table.RowState;
38  import org.apache.commons.lang.ObjectUtils;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  
43  /**
44   * Manages iteration for a Table.  Different cursors provide different methods
45   * of traversing a table.  Cursors should be fairly robust in the face of
46   * table modification during traversal (although depending on how the table is
47   * traversed, row updates may or may not be seen).  Multiple cursors may
48   * traverse the same table simultaneously.
49   * <p>
50   * The Cursor provides a variety of static utility methods to construct
51   * cursors with given characteristics or easily search for specific values.
52   * For even friendlier and more flexible construction, see
53   * {@link CursorBuilder}.
54   * <p>
55   * Is not thread-safe.
56   *
57   * @author James Ahlborn
58   */
59  public abstract class Cursor implements Iterable<Map<String, Object>>
60  {  
61    private static final Log LOG = LogFactory.getLog(Cursor.class);  
62  
63    /** boolean value indicating forward movement */
64    public static final boolean MOVE_FORWARD = true;
65    /** boolean value indicating reverse movement */
66    public static final boolean MOVE_REVERSE = false;
67    
68    /** first position for the TableScanCursor */
69    private static final ScanPosition FIRST_SCAN_POSITION =
70      new ScanPosition(RowId.FIRST_ROW_ID);
71    /** last position for the TableScanCursor */
72    private static final ScanPosition LAST_SCAN_POSITION =
73      new ScanPosition(RowId.LAST_ROW_ID);
74  
75    /** identifier for this cursor */
76    private final Id _id;
77    /** owning table */
78    private final Table _table;
79    /** State used for reading the table rows */
80    private final RowState _rowState;
81    /** the first (exclusive) row id for this cursor */
82    private final Position _firstPos;
83    /** the last (exclusive) row id for this cursor */
84    private final Position _lastPos;
85    /** the previous row */
86    private Position _prevPos;
87    /** the current row */
88    private Position _curPos;
89    
90  
91    protected Cursor(Id id, Table table, Position firstPos, Position lastPos) {
92      _id = id;
93      _table = table;
94      _rowState = _table.createRowState();
95      _firstPos = firstPos;
96      _lastPos = lastPos;
97      _curPos = firstPos;
98      _prevPos = firstPos;
99    }
100 
101   /**
102    * Creates a normal, un-indexed cursor for the given table.
103    * @param table the table over which this cursor will traverse
104    */
105   public static Cursor createCursor(Table table) {
106     return new TableScanCursor(table);
107   }
108 
109   /**
110    * Creates an indexed cursor for the given table.
111    * <p>
112    * Note, index based table traversal may not include all rows, as certain
113    * types of indexes do not include all entries (namely, some indexes ignore
114    * null entries, see {@link Index#shouldIgnoreNulls}).
115    * 
116    * @param table the table over which this cursor will traverse
117    * @param index index for the table which will define traversal order as
118    *              well as enhance certain lookups
119    */
120   public static Cursor createIndexCursor(Table table, Index index)
121     throws IOException
122   {
123     return createIndexCursor(table, index, null, null);
124   }
125   
126   /**
127    * Creates an indexed cursor for the given table, narrowed to the given
128    * range.
129    * <p>
130    * Note, index based table traversal may not include all rows, as certain
131    * types of indexes do not include all entries (namely, some indexes ignore
132    * null entries, see {@link Index#shouldIgnoreNulls}).
133    * 
134    * @param table the table over which this cursor will traverse
135    * @param index index for the table which will define traversal order as
136    *              well as enhance certain lookups
137    * @param startRow the first row of data for the cursor (inclusive), or
138    *                 {@code null} for the first entry
139    * @param endRow the last row of data for the cursor (inclusive), or
140    *               {@code null} for the last entry
141    */
142   public static Cursor createIndexCursor(Table table, Index index,
143                                          Object[] startRow, Object[] endRow)
144     throws IOException
145   {
146     return createIndexCursor(table, index, startRow, true, endRow, true);
147   }
148   
149   /**
150    * Creates an indexed cursor for the given table, narrowed to the given
151    * range.
152    * <p>
153    * Note, index based table traversal may not include all rows, as certain
154    * types of indexes do not include all entries (namely, some indexes ignore
155    * null entries, see {@link Index#shouldIgnoreNulls}).
156    * 
157    * @param table the table over which this cursor will traverse
158    * @param index index for the table which will define traversal order as
159    *              well as enhance certain lookups
160    * @param startRow the first row of data for the cursor, or {@code null} for
161    *                 the first entry
162    * @param startInclusive whether or not startRow is inclusive or exclusive
163    * @param endRow the last row of data for the cursor, or {@code null} for
164    *               the last entry
165    * @param endInclusive whether or not endRow is inclusive or exclusive
166    */
167   public static Cursor createIndexCursor(Table table, Index index,
168                                          Object[] startRow,
169                                          boolean startInclusive,
170                                          Object[] endRow,
171                                          boolean endInclusive)
172     throws IOException
173   {
174     if(table != index.getTable()) {
175       throw new IllegalArgumentException(
176           "Given index is not for given table: " + index + ", " + table);
177     }
178     return new IndexCursor(table, index,
179                            index.cursor(startRow, startInclusive,
180                                         endRow, endInclusive));
181   }
182 
183   /**
184    * Convenience method for finding a specific row in a table which matches a
185    * given row "pattern".  See {@link #findRow(Map)} for details on the
186    * rowPattern.
187    * 
188    * @param table the table to search
189    * @param rowPattern pattern to be used to find the row
190    * @return the matching row or {@code null} if a match could not be found.
191    */
192   public static Map<String,Object> findRow(Table table,
193                                            Map<String,Object> rowPattern)
194     throws IOException
195   {
196     Cursor cursor = createCursor(table);
197     if(cursor.findRow(rowPattern)) {
198       return cursor.getCurrentRow();
199     }
200     return null;
201   }
202   
203   /**
204    * Convenience method for finding a specific row in a table which matches a
205    * given row "pattern".  See {@link #findRow(Column,Object)} for details on
206    * the pattern.
207    * <p>
208    * Note, a {@code null} result value is ambiguous in that it could imply no
209    * match or a matching row with {@code null} for the desired value.  If
210    * distinguishing this situation is important, you will need to use a Cursor
211    * directly instead of this convenience method.
212    * 
213    * @param table the table to search
214    * @param column column whose value should be returned
215    * @param columnPattern column being matched by the valuePattern
216    * @param valuePattern value from the columnPattern which will match the
217    *                     desired row
218    * @return the matching row or {@code null} if a match could not be found.
219    */
220   public static Object findValue(Table table, Column column,
221                                  Column columnPattern, Object valuePattern)
222     throws IOException
223   {
224     Cursor cursor = createCursor(table);
225     if(cursor.findRow(columnPattern, valuePattern)) {
226       return cursor.getCurrentRowValue(column);
227     }
228     return null;
229   }
230   
231   /**
232    * Convenience method for finding a specific row in an indexed table which
233    * matches a given row "pattern".  See {@link #findRow(Map)} for details on
234    * the rowPattern.
235    * 
236    * @param table the table to search
237    * @param index index to assist the search
238    * @param rowPattern pattern to be used to find the row
239    * @return the matching row or {@code null} if a match could not be found.
240    */
241   public static Map<String,Object> findRow(Table table, Index index,
242                                            Map<String,Object> rowPattern)
243     throws IOException
244   {
245     Cursor cursor = createIndexCursor(table, index);
246     if(cursor.findRow(rowPattern)) {
247       return cursor.getCurrentRow();
248     }
249     return null;
250   }
251   
252   /**
253    * Convenience method for finding a specific row in a table which matches a
254    * given row "pattern".  See {@link #findRow(Column,Object)} for details on
255    * the pattern.
256    * <p>
257    * Note, a {@code null} result value is ambiguous in that it could imply no
258    * match or a matching row with {@code null} for the desired value.  If
259    * distinguishing this situation is important, you will need to use a Cursor
260    * directly instead of this convenience method.
261    * 
262    * @param table the table to search
263    * @param index index to assist the search
264    * @param column column whose value should be returned
265    * @param columnPattern column being matched by the valuePattern
266    * @param valuePattern value from the columnPattern which will match the
267    *                     desired row
268    * @return the matching row or {@code null} if a match could not be found.
269    */
270   public static Object findValue(Table table, Index index, Column column,
271                                  Column columnPattern, Object valuePattern)
272     throws IOException
273   {
274     Cursor cursor = createIndexCursor(table, index);
275     if(cursor.findRow(columnPattern, valuePattern)) {
276       return cursor.getCurrentRowValue(column);
277     }
278     return null;
279   }
280   
281   public Id getId() {
282     return _id;
283   }
284 
285   public Table getTable() {
286     return _table;
287   }
288 
289   public Index getIndex() {
290     return null;
291   }
292   
293   public JetFormat getFormat() {
294     return getTable().getFormat();
295   }
296 
297   public PageChannel getPageChannel() {
298     return getTable().getPageChannel();
299   }
300 
301   /**
302    * Returns the current state of the cursor which can be restored at a future
303    * point in time by a call to {@link #restoreSavepoint}.
304    * <p>
305    * Savepoints may be used across different cursor instances for the same
306    * table, but they must have the same {@link Id}.
307    */
308   public Savepoint getSavepoint() {
309     return new Savepoint(_id, _curPos, _prevPos);
310   }
311 
312   /**
313    * Moves the cursor to a savepoint previously returned from
314    * {@link #getSavepoint}.
315    * @throws IllegalArgumentException if the given savepoint does not have a
316    *         cursorId equal to this cursor's id
317    */
318   public void restoreSavepoint(Savepoint savepoint)
319     throws IOException
320   {
321     if(!_id.equals(savepoint.getCursorId())) {
322       throw new IllegalArgumentException(
323           "Savepoint " + savepoint + " is not valid for this cursor with id "
324           + _id);
325     }
326     restorePosition(savepoint.getCurrentPosition(),
327                     savepoint.getPreviousPosition());
328   }
329   
330   /**
331    * Returns the first row id (exclusive) as defined by this cursor.
332    */
333   protected Position getFirstPosition() {
334     return _firstPos;
335   }
336   
337   /**
338    * Returns the last row id (exclusive) as defined by this cursor.
339    */
340   protected Position getLastPosition() {
341     return _lastPos;
342   }
343 
344   /**
345    * Resets this cursor for forward traversal.  Calls {@link #beforeFirst}.
346    */
347   public void reset() {
348     beforeFirst();
349   }  
350 
351   /**
352    * Resets this cursor for forward traversal (sets cursor to before the first
353    * row).
354    */
355   public void beforeFirst() {
356     reset(MOVE_FORWARD);
357   }
358   
359   /**
360    * Resets this cursor for reverse traversal (sets cursor to after the last
361    * row).
362    */
363   public void afterLast() {
364     reset(MOVE_REVERSE);
365   }
366 
367   /**
368    * Returns {@code true} if the cursor is currently positioned before the
369    * first row, {@code false} otherwise.
370    */
371   public boolean isBeforeFirst()
372     throws IOException
373   {
374     if(getFirstPosition().equals(_curPos)) {
375       return !recheckPosition(MOVE_REVERSE);
376     }
377     return false;
378   }
379   
380   /**
381    * Returns {@code true} if the cursor is currently positioned after the
382    * last row, {@code false} otherwise.
383    */
384   public boolean isAfterLast()
385     throws IOException
386   {
387     if(getLastPosition().equals(_curPos)) {
388       return !recheckPosition(MOVE_FORWARD);
389     }
390     return false;
391   }
392 
393   /**
394    * Returns {@code true} if the row at which the cursor is currently
395    * positioned is deleted, {@code false} otherwise (including invalid rows).
396    */
397   public boolean isCurrentRowDeleted()
398     throws IOException
399   {
400     // we need to ensure that the "deleted" flag has been read for this row
401     // (or re-read if the table has been recently modified)
402     Table.positionAtRowData(_rowState, _curPos.getRowId());
403     return _rowState.isDeleted();
404   }
405   
406   /**
407    * Resets this cursor for traversing the given direction.
408    */
409   protected void reset(boolean moveForward) {
410     _curPos = getDirHandler(moveForward).getBeginningPosition();
411     _prevPos = _curPos;
412     _rowState.reset();
413   }  
414 
415   /**
416    * Returns an Iterable whose iterator() method calls <code>afterLast</code>
417    * on this cursor and returns an unmodifiable Iterator which will iterate
418    * through all the rows of this table in reverse order.  Use of the Iterator
419    * follows the same restrictions as a call to <code>getPreviousRow</code>.
420    * @throws IllegalStateException if an IOException is thrown by one of the
421    *         operations, the actual exception will be contained within
422    */
423   public Iterable<Map<String, Object>> reverseIterable() {
424     return reverseIterable(null);
425   }
426   
427   /**
428    * Returns an Iterable whose iterator() method calls <code>afterLast</code>
429    * on this table and returns an unmodifiable Iterator which will iterate
430    * through all the rows of this table in reverse order, returning only the
431    * given columns.  Use of the Iterator follows the same restrictions as a
432    * call to <code>getPreviousRow</code>.
433    * @throws IllegalStateException if an IOException is thrown by one of the
434    *         operations, the actual exception will be contained within
435    */
436   public Iterable<Map<String, Object>> reverseIterable(
437       final Collection<String> columnNames)
438   {
439     return new Iterable<Map<String, Object>>() {
440       public Iterator<Map<String, Object>> iterator() {
441         return new RowIterator(columnNames, MOVE_REVERSE);
442       }
443     };
444   }
445   
446   /**
447    * Calls <code>beforeFirst</code> on this cursor and returns an unmodifiable
448    * Iterator which will iterate through all the rows of this table.  Use of
449    * the Iterator follows the same restrictions as a call to
450    * <code>getNextRow</code>.
451    * @throws IllegalStateException if an IOException is thrown by one of the
452    *         operations, the actual exception will be contained within
453    */
454   public Iterator<Map<String, Object>> iterator()
455   {
456     return iterator(null);
457   }
458   
459   /**
460    * Returns an Iterable whose iterator() method returns the result of a call
461    * to {@link #iterator(Collection)}
462    * @throws IllegalStateException if an IOException is thrown by one of the
463    *         operations, the actual exception will be contained within
464    */
465   public Iterable<Map<String, Object>> iterable(
466       final Collection<String> columnNames)
467   {
468     return new Iterable<Map<String, Object>>() {
469       public Iterator<Map<String, Object>> iterator() {
470         return Cursor.this.iterator(columnNames);
471       }
472     };
473   }
474   
475   /**
476    * Calls <code>beforeFirst</code> on this table and returns an unmodifiable
477    * Iterator which will iterate through all the rows of this table, returning
478    * only the given columns.  Use of the Iterator follows the same
479    * restrictions as a call to <code>getNextRow</code>.
480    * @throws IllegalStateException if an IOException is thrown by one of the
481    *         operations, the actual exception will be contained within
482    */
483   public Iterator<Map<String, Object>> iterator(Collection<String> columnNames)
484   {
485     return new RowIterator(columnNames, MOVE_FORWARD);
486   }
487 
488   /**
489    * Delete the current row.
490    * @throws IllegalStateException if the current row is not valid (at
491    *         beginning or end of table), or already deleted.
492    */
493   public void deleteCurrentRow() throws IOException {
494     _table.deleteRow(_rowState, _curPos.getRowId());
495   }
496 
497   /**
498    * Moves to the next row in the table and returns it.
499    * @return The next row in this table (Column name -> Column value), or
500    *         {@code null} if no next row is found
501    */
502   public Map<String, Object> getNextRow() throws IOException {
503     return getNextRow(null);
504   }
505 
506   /**
507    * Moves to the next row in the table and returns it.
508    * @param columnNames Only column names in this collection will be returned
509    * @return The next row in this table (Column name -> Column value), or
510    *         {@code null} if no next row is found
511    */
512   public Map<String, Object> getNextRow(Collection<String> columnNames) 
513     throws IOException
514   {
515     return getAnotherRow(columnNames, MOVE_FORWARD);
516   }
517 
518   /**
519    * Moves to the previous row in the table and returns it.
520    * @return The previous row in this table (Column name -> Column value), or
521    *         {@code null} if no previous row is found
522    */
523   public Map<String, Object> getPreviousRow() throws IOException {
524     return getPreviousRow(null);
525   }
526 
527   /**
528    * Moves to the previous row in the table and returns it.
529    * @param columnNames Only column names in this collection will be returned
530    * @return The previous row in this table (Column name -> Column value), or
531    *         {@code null} if no previous row is found
532    */
533   public Map<String, Object> getPreviousRow(Collection<String> columnNames) 
534     throws IOException
535   {
536     return getAnotherRow(columnNames, MOVE_REVERSE);
537   }
538 
539 
540   /**
541    * Moves to another row in the table based on the given direction and
542    * returns it.
543    * @param columnNames Only column names in this collection will be returned
544    * @return another row in this table (Column name -> Column value), where
545    *         "next" may be backwards if moveForward is {@code false}, or
546    *         {@code null} if there is not another row in the given direction.
547    */
548   private Map<String, Object> getAnotherRow(Collection<String> columnNames,
549                                             boolean moveForward) 
550     throws IOException
551   {
552     if(moveToAnotherRow(moveForward)) {
553       return getCurrentRow(columnNames);
554     }
555     return null;
556   }  
557 
558   /**
559    * Moves to the next row as defined by this cursor.
560    * @return {@code true} if a valid next row was found, {@code false}
561    *         otherwise
562    */
563   public boolean moveToNextRow()
564     throws IOException
565   {
566     return moveToAnotherRow(MOVE_FORWARD);
567   }
568 
569   /**
570    * Moves to the previous row as defined by this cursor.
571    * @return {@code true} if a valid previous row was found, {@code false}
572    *         otherwise
573    */
574   public boolean moveToPreviousRow()
575     throws IOException
576   {
577     return moveToAnotherRow(MOVE_REVERSE);
578   }
579 
580   /**
581    * Moves to another row in the given direction as defined by this cursor.
582    * @return {@code true} if another valid row was found in the given
583    *         direction, {@code false} otherwise
584    */
585   private boolean moveToAnotherRow(boolean moveForward)
586     throws IOException
587   {
588     if(_curPos.equals(getDirHandler(moveForward).getEndPosition())) {
589       // already at end, make sure nothing has changed
590       return recheckPosition(moveForward);
591     }
592 
593     return moveToAnotherRowImpl(moveForward);
594   }
595 
596   /**
597    * Restores a current position for the cursor (current position becomes
598    * previous position).
599    */
600   protected void restorePosition(Position curPos)
601     throws IOException
602   {
603     restorePosition(curPos, _curPos);
604   }
605     
606   /**
607    * Restores a current and previous position for the cursor if the given
608    * positions are different from the current positions.
609    */
610   protected final void restorePosition(Position curPos, Position prevPos)
611     throws IOException
612   {
613     if(!curPos.equals(_curPos) || !prevPos.equals(_prevPos)) {
614       restorePositionImpl(curPos, prevPos);
615     }
616   }
617 
618   /**
619    * Restores a current and previous position for the cursor.
620    */
621   protected void restorePositionImpl(Position curPos, Position prevPos)
622     throws IOException
623   {
624     // make the current position previous, and the new position current
625     _prevPos = _curPos;
626     _curPos = curPos;
627     _rowState.reset();
628   }
629   
630   /**
631    * Rechecks the current position if the underlying data structures have been
632    * modified.
633    * @return {@code true} if the cursor ended up in a new position,
634    *         {@code false} otherwise.
635    */
636   private boolean recheckPosition(boolean moveForward)
637     throws IOException
638   {
639     if(isUpToDate()) {
640       // nothing has changed
641       return false;
642     }
643 
644     // move the cursor back to the previous position
645     restorePosition(_prevPos);
646     return moveToAnotherRowImpl(moveForward);
647   }
648 
649   /**
650    * Does the grunt work of moving the cursor to another position in the given
651    * direction.
652    */
653   private boolean moveToAnotherRowImpl(boolean moveForward)
654     throws IOException
655   {
656     _rowState.reset();
657     _prevPos = _curPos;
658     _curPos = findAnotherPosition(_rowState, _curPos, moveForward);
659     Table.positionAtRowHeader(_rowState, _curPos.getRowId());
660     return(!_curPos.equals(getDirHandler(moveForward).getEndPosition()));
661   }
662   
663   /**
664    * Moves to the first row (as defined by the cursor) where the given column
665    * has the given value.  This may be more efficient on some cursors than
666    * others.  If a match is not found (or an exception is thrown), the cursor
667    * is restored to its previous state.
668    *
669    * @param columnPattern column from the table for this cursor which is being
670    *                      matched by the valuePattern
671    * @param valuePattern value which is equal to the corresponding value in
672    *                     the matched row
673    * @return {@code true} if a valid row was found with the given value,
674    *         {@code false} if no row was found
675    */
676   public boolean findRow(Column columnPattern, Object valuePattern)
677     throws IOException
678   {
679     Position curPos = _curPos;
680     Position prevPos = _prevPos;
681     boolean found = false;
682     try {
683       found = findRowImpl(columnPattern, valuePattern);
684       return found;
685     } finally {
686       if(!found) {
687         try {
688           restorePosition(curPos, prevPos);
689         } catch(IOException e) {
690           LOG.error("Failed restoring position", e);
691         }
692       }
693     }
694   }
695   
696   /**
697    * Moves to the first row (as defined by the cursor) where the given columns
698    * have the given values.  This may be more efficient on some cursors than
699    * others.  If a match is not found (or an exception is thrown), the cursor
700    * is restored to its previous state.
701    *
702    * @param rowPattern column names and values which must be equal to the
703    *                   corresponding values in the matched row
704    * @return {@code true} if a valid row was found with the given values,
705    *         {@code false} if no row was found
706    */
707   public boolean findRow(Map<String,Object> rowPattern)
708     throws IOException
709   {
710     Position curPos = _curPos;
711     Position prevPos = _prevPos;
712     boolean found = false;
713     try {
714       found = findRowImpl(rowPattern);
715       return found;
716     } finally {
717       if(!found) {
718         try {
719           restorePosition(curPos, prevPos);
720         } catch(IOException e) {
721           LOG.error("Failed restoring position", e);
722         }
723       }
724     }
725   }
726 
727   /**
728    * Returns {@code true} if the current row matches the given pattern.
729    * @param columnPattern column from the table for this cursor which is being
730    *                      matched by the valuePattern
731    * @param valuePattern value which is tested for equality with the
732    *                     corresponding value in the current row
733    */
734   public boolean currentRowMatches(Column columnPattern, Object valuePattern)
735     throws IOException
736   {
737     return ObjectUtils.equals(valuePattern, getCurrentRowValue(columnPattern));
738   }
739   
740   /**
741    * Returns {@code true} if the current row matches the given pattern.
742    * @param rowPattern column names and values which must be equal to the
743    *                   corresponding values in the current row
744    */
745   public boolean currentRowMatches(Map<String,Object> rowPattern)
746     throws IOException
747   {
748     return ObjectUtils.equals(rowPattern, getCurrentRow(rowPattern.keySet()));
749   }
750   
751   /**
752    * Moves to the first row (as defined by the cursor) where the given column
753    * has the given value.  Caller manages save/restore on failure.
754    * <p>
755    * Default implementation scans the table from beginning to end.
756    *
757    * @param columnPattern column from the table for this cursor which is being
758    *                      matched by the valuePattern
759    * @param valuePattern value which is equal to the corresponding value in
760    *                     the matched row
761    * @return {@code true} if a valid row was found with the given value,
762    *         {@code false} if no row was found
763    */
764   protected boolean findRowImpl(Column columnPattern, Object valuePattern)
765     throws IOException
766   {
767     beforeFirst();
768     while(moveToNextRow()) {
769       if(currentRowMatches(columnPattern, valuePattern)) {
770         return true;
771       }
772     }
773     return false;
774   }
775 
776   /**
777    * Moves to the first row (as defined by the cursor) where the given columns
778    * have the given values.  Caller manages save/restore on failure.
779    * <p>
780    * Default implementation scans the table from beginning to end.
781    *
782    * @param rowPattern column names and values which must be equal to the
783    *                   corresponding values in the matched row
784    * @return {@code true} if a valid row was found with the given values,
785    *         {@code false} if no row was found
786    */
787   protected boolean findRowImpl(Map<String,Object> rowPattern)
788     throws IOException
789   {
790     beforeFirst();
791     while(moveToNextRow()) {
792       if(currentRowMatches(rowPattern)) {
793         return true;
794       }
795     }
796     return false;
797   }  
798 
799   /**
800    * Moves forward as many rows as possible up to the given number of rows.
801    * @return the number of rows moved.
802    */
803   public int moveNextRows(int numRows)
804     throws IOException
805   {
806     return moveSomeRows(numRows, MOVE_FORWARD);
807   }
808 
809   /**
810    * Moves backward as many rows as possible up to the given number of rows.
811    * @return the number of rows moved.
812    */
813   public int movePreviousRows(int numRows)
814     throws IOException
815   {
816     return moveSomeRows(numRows, MOVE_REVERSE);
817   }
818 
819   /**
820    * Moves as many rows as possible in the given direction up to the given
821    * number of rows.
822    * @return the number of rows moved.
823    */
824   private int moveSomeRows(int numRows, boolean moveForward)
825     throws IOException
826   {
827     int numMovedRows = 0;
828     while((numMovedRows < numRows) && moveToAnotherRow(moveForward)) {
829       ++numMovedRows;
830     }
831     return numMovedRows;
832   }
833 
834   /**
835    * Returns the current row in this cursor (Column name -> Column value).
836    */
837   public Map<String, Object> getCurrentRow()
838     throws IOException
839   {
840     return getCurrentRow(null);
841   }
842 
843   /**
844    * Returns the current row in this cursor (Column name -> Column value).
845    * @param columnNames Only column names in this collection will be returned
846    */
847   public Map<String, Object> getCurrentRow(Collection<String> columnNames)
848     throws IOException
849   {
850     return _table.getRow(_rowState, _curPos.getRowId(), columnNames);
851   }
852 
853   /**
854    * Returns the given column from the current row.
855    */
856   public Object getCurrentRowValue(Column column)
857     throws IOException
858   {
859     return _table.getRowValue(_rowState, _curPos.getRowId(), column);
860   }
861 
862   /**
863    * Returns {@code true} if this cursor is up-to-date with respect to the
864    * relevant table and related table objects, {@code false} otherwise.
865    */
866   protected boolean isUpToDate() {
867     return _rowState.isUpToDate();
868   }
869 
870   @Override
871   public String toString() {
872     return getClass().getSimpleName() + " CurPosition " + _curPos +
873       ", PrevPosition " + _prevPos;
874   }
875     
876   /**
877    * Finds the next non-deleted row after the given row (as defined by this
878    * cursor) and returns the id of the row, where "next" may be backwards if
879    * moveForward is {@code false}.  If there are no more rows, the returned
880    * rowId should equal the value returned by {@link #getLastPosition} if moving
881    * forward and {@link #getFirstPosition} if moving backward.
882    */
883   protected abstract Position findAnotherPosition(RowState rowState,
884                                                   Position curPos,
885                                                   boolean moveForward)
886     throws IOException;
887 
888   /**
889    * Returns the DirHandler for the given movement direction.
890    */
891   protected abstract DirHandler getDirHandler(boolean moveForward);
892 
893   /**
894    * Row iterator for this table, unmodifiable.
895    */
896   private final class RowIterator implements Iterator<Map<String, Object>>
897   {
898     private final Collection<String> _columnNames;
899     private final boolean _moveForward;
900     private boolean _hasNext = false;
901     
902     private RowIterator(Collection<String> columnNames, boolean moveForward)
903     {
904       try {
905         _columnNames = columnNames;
906         _moveForward = moveForward;
907         reset(_moveForward);
908         _hasNext = moveToAnotherRow(_moveForward);
909       } catch(IOException e) {
910         throw new IllegalStateException(e);
911       }
912     }
913 
914     public boolean hasNext() { return _hasNext; }
915 
916     public void remove() {
917       throw new UnsupportedOperationException();
918     }
919     
920     public Map<String, Object> next() {
921       if(!hasNext()) {
922         throw new NoSuchElementException();
923       }
924       try {
925         Map<String, Object> rtn = getCurrentRow(_columnNames);
926         _hasNext = moveToAnotherRow(_moveForward);
927         return rtn;
928       } catch(IOException e) {
929         throw new IllegalStateException(e);
930       }
931     }
932     
933   }
934 
935   /**
936    * Handles moving the cursor in a given direction.  Separates cursor
937    * logic from value storage.
938    */
939   protected abstract class DirHandler
940   {
941     public abstract Position getBeginningPosition();
942     public abstract Position getEndPosition();
943   }
944 
945   
946   /**
947    * Simple un-indexed cursor.
948    */
949   private static final class TableScanCursor extends Cursor
950   {
951     /** ScanDirHandler for forward traversal */
952     private final ScanDirHandler _forwardDirHandler =
953       new ForwardScanDirHandler();
954     /** ScanDirHandler for backward traversal */
955     private final ScanDirHandler _reverseDirHandler =
956       new ReverseScanDirHandler();
957     /** Cursor over the pages that this table owns */
958     private final UsageMap.PageCursor _ownedPagesCursor;
959     
960     private TableScanCursor(Table table) {
961       super(new Id(table, null), table,
962             FIRST_SCAN_POSITION, LAST_SCAN_POSITION);
963       _ownedPagesCursor = table.getOwnedPagesCursor();
964     }
965 
966     @Override
967     protected ScanDirHandler getDirHandler(boolean moveForward) {
968       return (moveForward ? _forwardDirHandler : _reverseDirHandler);
969     }
970 
971     @Override
972     protected boolean isUpToDate() {
973       return(super.isUpToDate() && _ownedPagesCursor.isUpToDate());
974     }
975     
976     @Override
977     protected void reset(boolean moveForward) {
978       _ownedPagesCursor.reset(moveForward);
979       super.reset(moveForward);
980     }
981 
982     @Override
983     protected void restorePositionImpl(Position curPos, Position prevPos)
984       throws IOException
985     {
986       if(!(curPos instanceof ScanPosition) ||
987          !(prevPos instanceof ScanPosition)) {
988         throw new IllegalArgumentException(
989             "Restored positions must be scan positions");
990       }
991       _ownedPagesCursor.restorePosition(curPos.getRowId().getPageNumber(),
992                                         prevPos.getRowId().getPageNumber());
993       super.restorePositionImpl(curPos, prevPos);
994     }
995 
996     @Override
997     protected Position findAnotherPosition(RowState rowState, Position curPos,
998                                            boolean moveForward)
999       throws IOException
1000     {
1001       ScanDirHandler handler = getDirHandler(moveForward);
1002       
1003       // figure out how many rows are left on this page so we can find the
1004       // next row
1005       RowId curRowId = curPos.getRowId();
1006       Table.positionAtRowHeader(rowState, curRowId);
1007       int currentRowNumber = curRowId.getRowNumber();
1008     
1009       // loop until we find the next valid row or run out of pages
1010       while(true) {
1011 
1012         currentRowNumber = handler.getAnotherRowNumber(currentRowNumber);
1013         curRowId = new RowId(curRowId.getPageNumber(), currentRowNumber);
1014         Table.positionAtRowHeader(rowState, curRowId);
1015         
1016         if(!rowState.isValid()) {
1017           
1018           // load next page
1019           curRowId = new RowId(handler.getAnotherPageNumber(),
1020                                RowId.INVALID_ROW_NUMBER);
1021           Table.positionAtRowHeader(rowState, curRowId);
1022           
1023           if(!rowState.isHeaderPageNumberValid()) {
1024             //No more owned pages.  No more rows.
1025             return handler.getEndPosition();
1026           }
1027 
1028           // update row count and initial row number
1029           currentRowNumber = handler.getInitialRowNumber(
1030               rowState.getRowsOnHeaderPage());
1031 
1032         } else if(!rowState.isDeleted()) {
1033           
1034           // we found a valid, non-deleted row, return it
1035           return new ScanPosition(curRowId);
1036         }
1037         
1038       }
1039     }
1040 
1041     /**
1042      * Handles moving the table scan cursor in a given direction.  Separates
1043      * cursor logic from value storage.
1044      */
1045     private abstract class ScanDirHandler extends DirHandler {
1046       public abstract int getAnotherRowNumber(int curRowNumber);
1047       public abstract int getAnotherPageNumber();
1048       public abstract int getInitialRowNumber(int rowsOnPage);
1049     }
1050     
1051     /**
1052      * Handles moving the table scan cursor forward.
1053      */
1054     private final class ForwardScanDirHandler extends ScanDirHandler {
1055       @Override
1056       public Position getBeginningPosition() {
1057         return getFirstPosition();
1058       }
1059       @Override
1060       public Position getEndPosition() {
1061         return getLastPosition();
1062       }
1063       @Override
1064       public int getAnotherRowNumber(int curRowNumber) {
1065         return curRowNumber + 1;
1066       }
1067       @Override
1068       public int getAnotherPageNumber() {
1069         return _ownedPagesCursor.getNextPage();
1070       }
1071       @Override
1072       public int getInitialRowNumber(int rowsOnPage) {
1073         return -1;
1074       }
1075     }
1076     
1077     /**
1078      * Handles moving the table scan cursor backward.
1079      */
1080     private final class ReverseScanDirHandler extends ScanDirHandler {
1081       @Override
1082       public Position getBeginningPosition() {
1083         return getLastPosition();
1084       }
1085       @Override
1086       public Position getEndPosition() {
1087         return getFirstPosition();
1088       }
1089       @Override
1090       public int getAnotherRowNumber(int curRowNumber) {
1091         return curRowNumber - 1;
1092       }
1093       @Override
1094       public int getAnotherPageNumber() {
1095         return _ownedPagesCursor.getPreviousPage();
1096       }
1097       @Override
1098       public int getInitialRowNumber(int rowsOnPage) {
1099         return rowsOnPage;
1100       }
1101     }
1102     
1103   }
1104 
1105   /**
1106    * Indexed cursor.
1107    */
1108   private static final class IndexCursor extends Cursor
1109   {
1110     /** IndexDirHandler for forward traversal */
1111     private final IndexDirHandler _forwardDirHandler =
1112       new ForwardIndexDirHandler();
1113     /** IndexDirHandler for backward traversal */
1114     private final IndexDirHandler _reverseDirHandler =
1115       new ReverseIndexDirHandler();
1116     /** Cursor over the entries of the relvant index */
1117     private final Index.EntryCursor _entryCursor;
1118 
1119     private IndexCursor(Table table, Index index,
1120                         Index.EntryCursor entryCursor)
1121       throws IOException
1122     {
1123       super(new Id(table, index), table,
1124             new IndexPosition(entryCursor.getFirstEntry()),
1125             new IndexPosition(entryCursor.getLastEntry()));
1126       _entryCursor = entryCursor;
1127     }
1128 
1129     @Override
1130     public Index getIndex() {
1131       return _entryCursor.getIndex();
1132     }
1133     
1134     @Override
1135     protected IndexDirHandler getDirHandler(boolean moveForward) {
1136       return (moveForward ? _forwardDirHandler : _reverseDirHandler);
1137     }
1138     
1139     @Override
1140     protected boolean isUpToDate() {
1141       return(super.isUpToDate() && _entryCursor.isUpToDate());
1142     }
1143     
1144     @Override
1145     protected void reset(boolean moveForward) {
1146       _entryCursor.reset(moveForward);
1147       super.reset(moveForward);
1148     }
1149 
1150     @Override
1151     protected void restorePositionImpl(Position curPos, Position prevPos)
1152       throws IOException
1153     {
1154       if(!(curPos instanceof IndexPosition) ||
1155          !(prevPos instanceof IndexPosition)) {
1156         throw new IllegalArgumentException(
1157             "Restored positions must be index positions");
1158       }
1159       _entryCursor.restorePosition(((IndexPosition)curPos).getEntry(),
1160                                    ((IndexPosition)prevPos).getEntry());
1161       super.restorePositionImpl(curPos, prevPos);
1162     }
1163 
1164     @Override
1165     protected boolean findRowImpl(Column columnPattern, Object valuePattern)
1166       throws IOException
1167     {
1168       Object[] rowValues = _entryCursor.getIndex().constructIndexRow(
1169           columnPattern.getName(), valuePattern);
1170 
1171       if(rowValues == null) {
1172         // bummer, use the default table scan
1173         return super.findRowImpl(columnPattern, valuePattern);
1174       }
1175       
1176       // sweet, we can use our index
1177       _entryCursor.beforeEntry(rowValues);
1178       Index.Entry startEntry = _entryCursor.getNextEntry();
1179       if(!startEntry.getRowId().isValid()) {
1180         // at end of index, no potential matches
1181         return false;
1182       }
1183 
1184       // either we found a row with the given value, or none exist in the
1185       // table
1186       restorePosition(new IndexPosition(startEntry));
1187       return currentRowMatches(columnPattern, valuePattern);
1188     }
1189 
1190     @Override
1191     protected boolean findRowImpl(Map<String,Object> rowPattern)
1192       throws IOException
1193     {
1194       Index index = _entryCursor.getIndex();
1195       Object[] rowValues = index.constructIndexRow(rowPattern);
1196 
1197       if(rowValues == null) {
1198         // bummer, use the default table scan
1199         return super.findRowImpl(rowPattern);
1200       }
1201       
1202       // sweet, we can use our index
1203       _entryCursor.beforeEntry(rowValues);
1204       Index.Entry startEntry = _entryCursor.getNextEntry();
1205       if(!startEntry.getRowId().isValid()) {
1206         // at end of index, no potential matches
1207         return false;
1208       }
1209       restorePosition(new IndexPosition(startEntry));
1210 
1211       Map<String,Object> indexRowPattern = null;
1212       if(rowPattern.size() == index.getColumns().size()) {
1213         // the rowPattern matches our index columns exactly, so we can
1214         // streamline our testing below
1215         indexRowPattern = rowPattern;
1216       } else {
1217         // the rowPattern has more columns than just the index, so we need to
1218         // do more work when testing below
1219         indexRowPattern =
1220           new LinkedHashMap<String,Object>();
1221         for(Index.ColumnDescriptor idxCol : index.getColumns()) {
1222           indexRowPattern.put(idxCol.getName(),
1223                               rowValues[idxCol.getColumnIndex()]);
1224         }
1225       }
1226       
1227       // there may be multiple columns which fit the pattern subset used by
1228       // the index, so we need to keep checking until our index values no
1229       // longer match
1230       do {
1231 
1232         if(!currentRowMatches(indexRowPattern)) {
1233           // there are no more rows which could possibly match
1234           break;
1235         }
1236 
1237         // note, if rowPattern == indexRowPattern, no need to do an extra
1238         // comparison with the current row
1239