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