0001: /*
0002: Copyright (c) 2007 Health Market Science, Inc.
0003:
0004: This library is free software; you can redistribute it and/or
0005: modify it under the terms of the GNU Lesser General Public
0006: License as published by the Free Software Foundation; either
0007: version 2.1 of the License, or (at your option) any later version.
0008:
0009: This library is distributed in the hope that it will be useful,
0010: but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: Lesser General Public License for more details.
0013:
0014: You should have received a copy of the GNU Lesser General Public
0015: License along with this library; if not, write to the Free Software
0016: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
0017: USA
0018:
0019: You can contact Health Market Science at info@healthmarketscience.com
0020: or at the following address:
0021:
0022: Health Market Science
0023: 2700 Horizon Drive
0024: Suite 200
0025: King of Prussia, PA 19406
0026: */
0027:
0028: package com.healthmarketscience.jackcess;
0029:
0030: import java.io.IOException;
0031: import java.util.Collection;
0032: import java.util.Iterator;
0033: import java.util.LinkedHashMap;
0034: import java.util.Map;
0035: import java.util.NoSuchElementException;
0036:
0037: import com.healthmarketscience.jackcess.Table.RowState;
0038: import org.apache.commons.lang.ObjectUtils;
0039: import org.apache.commons.logging.Log;
0040: import org.apache.commons.logging.LogFactory;
0041:
0042: /**
0043: * Manages iteration for a Table. Different cursors provide different methods
0044: * of traversing a table. Cursors should be fairly robust in the face of
0045: * table modification during traversal (although depending on how the table is
0046: * traversed, row updates may or may not be seen). Multiple cursors may
0047: * traverse the same table simultaneously.
0048: * <p>
0049: * The Cursor provides a variety of static utility methods to construct
0050: * cursors with given characteristics or easily search for specific values.
0051: * For even friendlier and more flexible construction, see
0052: * {@link CursorBuilder}.
0053: * <p>
0054: * Is not thread-safe.
0055: *
0056: * @author james
0057: */
0058: public abstract class Cursor implements Iterable<Map<String, Object>> {
0059: private static final Log LOG = LogFactory.getLog(Cursor.class);
0060:
0061: /** first position for the TableScanCursor */
0062: private static final ScanPosition FIRST_SCAN_POSITION = new ScanPosition(
0063: RowId.FIRST_ROW_ID);
0064: /** last position for the TableScanCursor */
0065: private static final ScanPosition LAST_SCAN_POSITION = new ScanPosition(
0066: RowId.LAST_ROW_ID);
0067:
0068: /** identifier for this cursor */
0069: private final Id _id;
0070: /** owning table */
0071: private final Table _table;
0072: /** State used for reading the table rows */
0073: private final RowState _rowState;
0074: /** the first (exclusive) row id for this cursor */
0075: private final Position _firstPos;
0076: /** the last (exclusive) row id for this cursor */
0077: private final Position _lastPos;
0078: /** the previous row */
0079: private Position _prevPos;
0080: /** the current row */
0081: private Position _curPos;
0082:
0083: protected Cursor(Id id, Table table, Position firstPos,
0084: Position lastPos) {
0085: _id = id;
0086: _table = table;
0087: _rowState = _table.createRowState();
0088: _firstPos = firstPos;
0089: _lastPos = lastPos;
0090: _curPos = firstPos;
0091: _prevPos = firstPos;
0092: }
0093:
0094: /**
0095: * Creates a normal, un-indexed cursor for the given table.
0096: * @param table the table over which this cursor will traverse
0097: */
0098: public static Cursor createCursor(Table table) {
0099: return new TableScanCursor(table);
0100: }
0101:
0102: /**
0103: * Creates an indexed cursor for the given table.
0104: * @param table the table over which this cursor will traverse
0105: * @param index index for the table which will define traversal order as
0106: * well as enhance certain lookups
0107: */
0108: public static Cursor createIndexCursor(Table table, Index index)
0109: throws IOException {
0110: return createIndexCursor(table, index, null, null);
0111: }
0112:
0113: /**
0114: * Creates an indexed cursor for the given table, narrowed to the given
0115: * range.
0116: * @param table the table over which this cursor will traverse
0117: * @param index index for the table which will define traversal order as
0118: * well as enhance certain lookups
0119: * @param startRow the first row of data for the cursor (inclusive), or
0120: * {@code null} for the first entry
0121: * @param endRow the last row of data for the cursor (inclusive), or
0122: * {@code null} for the last entry
0123: */
0124: public static Cursor createIndexCursor(Table table, Index index,
0125: Object[] startRow, Object[] endRow) throws IOException {
0126: return createIndexCursor(table, index, startRow, true, endRow,
0127: true);
0128: }
0129:
0130: /**
0131: * Creates an indexed cursor for the given table, narrowed to the given
0132: * range.
0133: * @param table the table over which this cursor will traverse
0134: * @param index index for the table which will define traversal order as
0135: * well as enhance certain lookups
0136: * @param startRow the first row of data for the cursor, or {@code null} for
0137: * the first entry
0138: * @param startInclusive whether or not startRow is inclusive or exclusive
0139: * @param endRow the last row of data for the cursor, or {@code null} for
0140: * the last entry
0141: * @param endInclusive whether or not endRow is inclusive or exclusive
0142: */
0143: public static Cursor createIndexCursor(Table table, Index index,
0144: Object[] startRow, boolean startInclusive, Object[] endRow,
0145: boolean endInclusive) throws IOException {
0146: if (table != index.getTable()) {
0147: throw new IllegalArgumentException(
0148: "Given index is not for given table: " + index
0149: + ", " + table);
0150: }
0151: return new IndexCursor(table, index, index.cursor(startRow,
0152: startInclusive, endRow, endInclusive));
0153: }
0154:
0155: /**
0156: * Convenience method for finding a specific row in a table which matches a
0157: * given row "pattern". See {@link #findRow(Map)} for details on the
0158: * rowPattern.
0159: *
0160: * @param table the table to search
0161: * @param rowPattern pattern to be used to find the row
0162: * @return the matching row or {@code null} if a match could not be found.
0163: */
0164: public static Map<String, Object> findRow(Table table,
0165: Map<String, Object> rowPattern) throws IOException {
0166: Cursor cursor = createCursor(table);
0167: if (cursor.findRow(rowPattern)) {
0168: return cursor.getCurrentRow();
0169: }
0170: return null;
0171: }
0172:
0173: /**
0174: * Convenience method for finding a specific row in a table which matches a
0175: * given row "pattern". See {@link #findRow(Column,Object)} for details on
0176: * the pattern.
0177: * <p>
0178: * Note, a {@code null} result value is ambiguous in that it could imply no
0179: * match or a matching row with {@code null} for the desired value. If
0180: * distinguishing this situation is important, you will need to use a Cursor
0181: * directly instead of this convenience method.
0182: *
0183: * @param table the table to search
0184: * @param column column whose value should be returned
0185: * @param columnPattern column being matched by the valuePattern
0186: * @param valuePattern value from the columnPattern which will match the
0187: * desired row
0188: * @return the matching row or {@code null} if a match could not be found.
0189: */
0190: public static Object findValue(Table table, Column column,
0191: Column columnPattern, Object valuePattern)
0192: throws IOException {
0193: Cursor cursor = createCursor(table);
0194: if (cursor.findRow(columnPattern, valuePattern)) {
0195: return cursor.getCurrentRowValue(column);
0196: }
0197: return null;
0198: }
0199:
0200: /**
0201: * Convenience method for finding a specific row in an indexed table which
0202: * matches a given row "pattern". See {@link #findRow(Map)} for details on
0203: * the rowPattern.
0204: *
0205: * @param table the table to search
0206: * @param index index to assist the search
0207: * @param rowPattern pattern to be used to find the row
0208: * @return the matching row or {@code null} if a match could not be found.
0209: */
0210: public static Map<String, Object> findRow(Table table, Index index,
0211: Map<String, Object> rowPattern) throws IOException {
0212: Cursor cursor = createIndexCursor(table, index);
0213: if (cursor.findRow(rowPattern)) {
0214: return cursor.getCurrentRow();
0215: }
0216: return null;
0217: }
0218:
0219: /**
0220: * Convenience method for finding a specific row in a table which matches a
0221: * given row "pattern". See {@link #findRow(Column,Object)} for details on
0222: * the pattern.
0223: * <p>
0224: * Note, a {@code null} result value is ambiguous in that it could imply no
0225: * match or a matching row with {@code null} for the desired value. If
0226: * distinguishing this situation is important, you will need to use a Cursor
0227: * directly instead of this convenience method.
0228: *
0229: * @param table the table to search
0230: * @param index index to assist the search
0231: * @param column column whose value should be returned
0232: * @param columnPattern column being matched by the valuePattern
0233: * @param valuePattern value from the columnPattern which will match the
0234: * desired row
0235: * @return the matching row or {@code null} if a match could not be found.
0236: */
0237: public static Object findValue(Table table, Index index,
0238: Column column, Column columnPattern, Object valuePattern)
0239: throws IOException {
0240: Cursor cursor = createIndexCursor(table, index);
0241: if (cursor.findRow(columnPattern, valuePattern)) {
0242: return cursor.getCurrentRowValue(column);
0243: }
0244: return null;
0245: }
0246:
0247: public Id getId() {
0248: return _id;
0249: }
0250:
0251: public Table getTable() {
0252: return _table;
0253: }
0254:
0255: public Index getIndex() {
0256: return null;
0257: }
0258:
0259: public JetFormat getFormat() {
0260: return getTable().getFormat();
0261: }
0262:
0263: public PageChannel getPageChannel() {
0264: return getTable().getPageChannel();
0265: }
0266:
0267: /**
0268: * Returns the current state of the cursor which can be restored at a future
0269: * point in time by a call to {@link #restoreSavepoint}.
0270: * <p>
0271: * Savepoints may be used across different cursor instances for the same
0272: * table, but they must have the same {@link Id}.
0273: */
0274: public Savepoint getSavepoint() {
0275: return new Savepoint(_id, _curPos, _prevPos);
0276: }
0277:
0278: /**
0279: * Moves the cursor to a savepoint previously returned from
0280: * {@link #getSavepoint}.
0281: * @throws IllegalArgumentException if the given savepoint does not have a
0282: * cursorId equal to this cursor's id
0283: */
0284: public void restoreSavepoint(Savepoint savepoint)
0285: throws IOException {
0286: if (!_id.equals(savepoint.getCursorId())) {
0287: throw new IllegalArgumentException("Savepoint " + savepoint
0288: + " is not valid for this cursor with id " + _id);
0289: }
0290: restorePosition(savepoint.getCurrentPosition(), savepoint
0291: .getPreviousPosition());
0292: }
0293:
0294: /**
0295: * Returns the first row id (exclusive) as defined by this cursor.
0296: */
0297: protected Position getFirstPosition() {
0298: return _firstPos;
0299: }
0300:
0301: /**
0302: * Returns the last row id (exclusive) as defined by this cursor.
0303: */
0304: protected Position getLastPosition() {
0305: return _lastPos;
0306: }
0307:
0308: /**
0309: * Resets this cursor for forward traversal. Calls {@link #beforeFirst}.
0310: */
0311: public void reset() {
0312: beforeFirst();
0313: }
0314:
0315: /**
0316: * Resets this cursor for forward traversal (sets cursor to before the first
0317: * row).
0318: */
0319: public void beforeFirst() {
0320: reset(true);
0321: }
0322:
0323: /**
0324: * Resets this cursor for reverse traversal (sets cursor to after the last
0325: * row).
0326: */
0327: public void afterLast() {
0328: reset(false);
0329: }
0330:
0331: /**
0332: * Returns {@code true} if the cursor is currently positioned before the
0333: * first row, {@code false} otherwise.
0334: */
0335: public boolean isBeforeFirst() throws IOException {
0336: if (getFirstPosition().equals(_curPos)) {
0337: return !recheckPosition(false);
0338: }
0339: return false;
0340: }
0341:
0342: /**
0343: * Returns {@code true} if the cursor is currently positioned after the
0344: * last row, {@code false} otherwise.
0345: */
0346: public boolean isAfterLast() throws IOException {
0347: if (getLastPosition().equals(_curPos)) {
0348: return !recheckPosition(true);
0349: }
0350: return false;
0351: }
0352:
0353: /**
0354: * Returns {@code true} if the row at which the cursor is currently
0355: * positioned is deleted, {@code false} otherwise (including invalid rows).
0356: */
0357: public boolean isCurrentRowDeleted() throws IOException {
0358: // we need to ensure that the "deleted" flag has been read for this row
0359: // (or re-read if the table has been recently modified)
0360: Table.positionAtRowData(_rowState, _curPos.getRowId());
0361: return _rowState.isDeleted();
0362: }
0363:
0364: /**
0365: * Resets this cursor for traversing the given direction.
0366: */
0367: protected void reset(boolean moveForward) {
0368: _curPos = getDirHandler(moveForward).getBeginningPosition();
0369: _prevPos = _curPos;
0370: _rowState.reset();
0371: }
0372:
0373: /**
0374: * Returns an Iterable whose iterator() method calls <code>afterLast</code>
0375: * on this cursor and returns an unmodifiable Iterator which will iterate
0376: * through all the rows of this table in reverse order. Use of the Iterator
0377: * follows the same restrictions as a call to <code>getPreviousRow</code>.
0378: * @throws IllegalStateException if an IOException is thrown by one of the
0379: * operations, the actual exception will be contained within
0380: */
0381: public Iterable<Map<String, Object>> reverseIterable() {
0382: return reverseIterable(null);
0383: }
0384:
0385: /**
0386: * Returns an Iterable whose iterator() method calls <code>afterLast</code>
0387: * on this table and returns an unmodifiable Iterator which will iterate
0388: * through all the rows of this table in reverse order, returning only the
0389: * given columns. Use of the Iterator follows the same restrictions as a
0390: * call to <code>getPreviousRow</code>.
0391: * @throws IllegalStateException if an IOException is thrown by one of the
0392: * operations, the actual exception will be contained within
0393: */
0394: public Iterable<Map<String, Object>> reverseIterable(
0395: final Collection<String> columnNames) {
0396: return new Iterable<Map<String, Object>>() {
0397: public Iterator<Map<String, Object>> iterator() {
0398: return new RowIterator(columnNames, false);
0399: }
0400: };
0401: }
0402:
0403: /**
0404: * Calls <code>beforeFirst</code> on this cursor and returns an unmodifiable
0405: * Iterator which will iterate through all the rows of this table. Use of
0406: * the Iterator follows the same restrictions as a call to
0407: * <code>getNextRow</code>.
0408: * @throws IllegalStateException if an IOException is thrown by one of the
0409: * operations, the actual exception will be contained within
0410: */
0411: public Iterator<Map<String, Object>> iterator() {
0412: return iterator(null);
0413: }
0414:
0415: /**
0416: * Returns an Iterable whose iterator() method returns the result of a call
0417: * to {@link #iterator(Collection<String>)}
0418: * @throws IllegalStateException if an IOException is thrown by one of the
0419: * operations, the actual exception will be contained within
0420: */
0421: public Iterable<Map<String, Object>> iterable(
0422: final Collection<String> columnNames) {
0423: return new Iterable<Map<String, Object>>() {
0424: public Iterator<Map<String, Object>> iterator() {
0425: return Cursor.this .iterator(columnNames);
0426: }
0427: };
0428: }
0429:
0430: /**
0431: * Calls <code>beforeFirst</code> on this table and returns an unmodifiable
0432: * Iterator which will iterate through all the rows of this table, returning
0433: * only the given columns. Use of the Iterator follows the same
0434: * restrictions as a call to <code>getNextRow</code>.
0435: * @throws IllegalStateException if an IOException is thrown by one of the
0436: * operations, the actual exception will be contained within
0437: */
0438: public Iterator<Map<String, Object>> iterator(
0439: Collection<String> columnNames) {
0440: return new RowIterator(columnNames, true);
0441: }
0442:
0443: /**
0444: * Delete the current row.
0445: * @throws IllegalStateException if the current row is not valid (at
0446: * beginning or end of table), or already deleted.
0447: */
0448: public void deleteCurrentRow() throws IOException {
0449: _table.deleteRow(_rowState, _curPos.getRowId());
0450: }
0451:
0452: /**
0453: * Moves to the next row in the table and returns it.
0454: * @return The next row in this table (Column name -> Column value), or
0455: * {@code null} if no next row is found
0456: */
0457: public Map<String, Object> getNextRow() throws IOException {
0458: return getNextRow(null);
0459: }
0460:
0461: /**
0462: * Moves to the next row in the table and returns it.
0463: * @param columnNames Only column names in this collection will be returned
0464: * @return The next row in this table (Column name -> Column value), or
0465: * {@code null} if no next row is found
0466: */
0467: public Map<String, Object> getNextRow(Collection<String> columnNames)
0468: throws IOException {
0469: return getAnotherRow(columnNames, true);
0470: }
0471:
0472: /**
0473: * Moves to the previous row in the table and returns it.
0474: * @return The previous row in this table (Column name -> Column value), or
0475: * {@code null} if no previous row is found
0476: */
0477: public Map<String, Object> getPreviousRow() throws IOException {
0478: return getPreviousRow(null);
0479: }
0480:
0481: /**
0482: * Moves to the previous row in the table and returns it.
0483: * @param columnNames Only column names in this collection will be returned
0484: * @return The previous row in this table (Column name -> Column value), or
0485: * {@code null} if no previous row is found
0486: */
0487: public Map<String, Object> getPreviousRow(
0488: Collection<String> columnNames) throws IOException {
0489: return getAnotherRow(columnNames, false);
0490: }
0491:
0492: /**
0493: * Moves to another row in the table based on the given direction and
0494: * returns it.
0495: * @param columnNames Only column names in this collection will be returned
0496: * @return another row in this table (Column name -> Column value), where
0497: * "next" may be backwards if moveForward is {@code false}, or
0498: * {@code null} if there is not another row in the given direction.
0499: */
0500: private Map<String, Object> getAnotherRow(
0501: Collection<String> columnNames, boolean moveForward)
0502: throws IOException {
0503: if (moveToAnotherRow(moveForward)) {
0504: return getCurrentRow(columnNames);
0505: }
0506: return null;
0507: }
0508:
0509: /**
0510: * Moves to the next row as defined by this cursor.
0511: * @return {@code true} if a valid next row was found, {@code false}
0512: * otherwise
0513: */
0514: public boolean moveToNextRow() throws IOException {
0515: return moveToAnotherRow(true);
0516: }
0517:
0518: /**
0519: * Moves to the previous row as defined by this cursor.
0520: * @return {@code true} if a valid previous row was found, {@code false}
0521: * otherwise
0522: */
0523: public boolean moveToPreviousRow() throws IOException {
0524: return moveToAnotherRow(false);
0525: }
0526:
0527: /**
0528: * Moves to another row in the given direction as defined by this cursor.
0529: * @return {@code true} if another valid row was found in the given
0530: * direction, {@code false} otherwise
0531: */
0532: private boolean moveToAnotherRow(boolean moveForward)
0533: throws IOException {
0534: if (_curPos.equals(getDirHandler(moveForward).getEndPosition())) {
0535: // already at end, make sure nothing has changed
0536: return recheckPosition(moveForward);
0537: }
0538:
0539: return moveToAnotherRowImpl(moveForward);
0540: }
0541:
0542: /**
0543: * Restores a current position for the cursor (current position becomes
0544: * previous position).
0545: */
0546: protected void restorePosition(Position curPos) throws IOException {
0547: restorePosition(curPos, _curPos);
0548: }
0549:
0550: /**
0551: * Restores a current and previous position for the cursor if the given
0552: * positions are different from the current positions.
0553: */
0554: protected final void restorePosition(Position curPos,
0555: Position prevPos) throws IOException {
0556: if (!curPos.equals(_curPos) || !prevPos.equals(_prevPos)) {
0557: restorePositionImpl(curPos, prevPos);
0558: }
0559: }
0560:
0561: /**
0562: * Restores a current and previous position for the cursor.
0563: */
0564: protected void restorePositionImpl(Position curPos, Position prevPos)
0565: throws IOException {
0566: // make the current position previous, and the new position current
0567: _prevPos = _curPos;
0568: _curPos = curPos;
0569: _rowState.reset();
0570: }
0571:
0572: /**
0573: * Rechecks the current position if the underlying data structures have been
0574: * modified.
0575: * @return {@code true} if the cursor ended up in a new position,
0576: * {@code false} otherwise.
0577: */
0578: private boolean recheckPosition(boolean moveForward)
0579: throws IOException {
0580: if (isUpToDate()) {
0581: // nothing has changed
0582: return false;
0583: }
0584:
0585: // move the cursor back to the previous position
0586: restorePosition(_prevPos);
0587: return moveToAnotherRowImpl(moveForward);
0588: }
0589:
0590: /**
0591: * Does the grunt work of moving the cursor to another position in the given
0592: * direction.
0593: */
0594: private boolean moveToAnotherRowImpl(boolean moveForward)
0595: throws IOException {
0596: _rowState.reset();
0597: _prevPos = _curPos;
0598: _curPos = findAnotherPosition(_rowState, _curPos, moveForward);
0599: Table.positionAtRowHeader(_rowState, _curPos.getRowId());
0600: return (!_curPos.equals(getDirHandler(moveForward)
0601: .getEndPosition()));
0602: }
0603:
0604: /**
0605: * Moves to the first row (as defined by the cursor) where the given column
0606: * has the given value. This may be more efficient on some cursors than
0607: * others. If a match is not found (or an exception is thrown), the cursor
0608: * is restored to its previous state.
0609: *
0610: * @param columnPattern column from the table for this cursor which is being
0611: * matched by the valuePattern
0612: * @param valuePattern value which is equal to the corresponding value in
0613: * the matched row
0614: * @return {@code true} if a valid row was found with the given value,
0615: * {@code false} if no row was found
0616: */
0617: public boolean findRow(Column columnPattern, Object valuePattern)
0618: throws IOException {
0619: Position curPos = _curPos;
0620: Position prevPos = _prevPos;
0621: boolean found = false;
0622: try {
0623: found = findRowImpl(columnPattern, valuePattern);
0624: return found;
0625: } finally {
0626: if (!found) {
0627: try {
0628: restorePosition(curPos, prevPos);
0629: } catch (IOException e) {
0630: LOG.error("Failed restoring position", e);
0631: }
0632: }
0633: }
0634: }
0635:
0636: /**
0637: * Moves to the first row (as defined by the cursor) where the given columns
0638: * have the given values. This may be more efficient on some cursors than
0639: * others. If a match is not found (or an exception is thrown), the cursor
0640: * is restored to its previous state.
0641: *
0642: * @param rowPattern column names and values which must be equal to the
0643: * corresponding values in the matched row
0644: * @return {@code true} if a valid row was found with the given values,
0645: * {@code false} if no row was found
0646: */
0647: public boolean findRow(Map<String, Object> rowPattern)
0648: throws IOException {
0649: Position curPos = _curPos;
0650: Position prevPos = _prevPos;
0651: boolean found = false;
0652: try {
0653: found = findRowImpl(rowPattern);
0654: return found;
0655: } finally {
0656: if (!found) {
0657: try {
0658: restorePosition(curPos, prevPos);
0659: } catch (IOException e) {
0660: LOG.error("Failed restoring position", e);
0661: }
0662: }
0663: }
0664: }
0665:
0666: /**
0667: * Returns {@code true} if the current row matches the given pattern.
0668: * @param columnPattern column from the table for this cursor which is being
0669: * matched by the valuePattern
0670: * @param valuePattern value which is tested for equality with the
0671: * corresponding value in the current row
0672: */
0673: public boolean currentRowMatches(Column columnPattern,
0674: Object valuePattern) throws IOException {
0675: return ObjectUtils.equals(valuePattern,
0676: getCurrentRowValue(columnPattern));
0677: }
0678:
0679: /**
0680: * Returns {@code true} if the current row matches the given pattern.
0681: * @param rowPattern column names and values which must be equal to the
0682: * corresponding values in the current row
0683: */
0684: public boolean currentRowMatches(Map<String, Object> rowPattern)
0685: throws IOException {
0686: return ObjectUtils.equals(rowPattern, getCurrentRow(rowPattern
0687: .keySet()));
0688: }
0689:
0690: /**
0691: * Moves to the first row (as defined by the cursor) where the given column
0692: * has the given value. Caller manages save/restore on failure.
0693: * <p>
0694: * Default implementation scans the table from beginning to end.
0695: *
0696: * @param columnPattern column from the table for this cursor which is being
0697: * matched by the valuePattern
0698: * @param valuePattern value which is equal to the corresponding value in
0699: * the matched row
0700: * @return {@code true} if a valid row was found with the given value,
0701: * {@code false} if no row was found
0702: */
0703: protected boolean findRowImpl(Column columnPattern,
0704: Object valuePattern) throws IOException {
0705: beforeFirst();
0706: while (moveToNextRow()) {
0707: if (currentRowMatches(columnPattern, valuePattern)) {
0708: return true;
0709: }
0710: }
0711: return false;
0712: }
0713:
0714: /**
0715: * Moves to the first row (as defined by the cursor) where the given columns
0716: * have the given values. Caller manages save/restore on failure.
0717: * <p>
0718: * Default implementation scans the table from beginning to end.
0719: *
0720: * @param rowPattern column names and values which must be equal to the
0721: * corresponding values in the matched row
0722: * @return {@code true} if a valid row was found with the given values,
0723: * {@code false} if no row was found
0724: */
0725: protected boolean findRowImpl(Map<String, Object> rowPattern)
0726: throws IOException {
0727: beforeFirst();
0728: while (moveToNextRow()) {
0729: if (currentRowMatches(rowPattern)) {
0730: return true;
0731: }
0732: }
0733: return false;
0734: }
0735:
0736: /**
0737: * Moves forward as many rows as possible up to the given number of rows.
0738: * @return the number of rows moved.
0739: */
0740: public int moveNextRows(int numRows) throws IOException {
0741: return moveSomeRows(numRows, true);
0742: }
0743:
0744: /**
0745: * Moves backward as many rows as possible up to the given number of rows.
0746: * @return the number of rows moved.
0747: */
0748: public int movePreviousRows(int numRows) throws IOException {
0749: return moveSomeRows(numRows, false);
0750: }
0751:
0752: /**
0753: * Moves as many rows as possible in the given direction up to the given
0754: * number of rows.
0755: * @return the number of rows moved.
0756: */
0757: private int moveSomeRows(int numRows, boolean moveForward)
0758: throws IOException {
0759: int numMovedRows = 0;
0760: while ((numMovedRows < numRows)
0761: && moveToAnotherRow(moveForward)) {
0762: ++numMovedRows;
0763: }
0764: return numMovedRows;
0765: }
0766:
0767: /**
0768: * Returns the current row in this cursor (Column name -> Column value).
0769: */
0770: public Map<String, Object> getCurrentRow() throws IOException {
0771: return getCurrentRow(null);
0772: }
0773:
0774: /**
0775: * Returns the current row in this cursor (Column name -> Column value).
0776: * @param columnNames Only column names in this collection will be returned
0777: */
0778: public Map<String, Object> getCurrentRow(
0779: Collection<String> columnNames) throws IOException {
0780: return _table
0781: .getRow(_rowState, _curPos.getRowId(), columnNames);
0782: }
0783:
0784: /**
0785: * Returns the given column from the current row.
0786: */
0787: public Object getCurrentRowValue(Column column) throws IOException {
0788: return _table
0789: .getRowValue(_rowState, _curPos.getRowId(), column);
0790: }
0791:
0792: /**
0793: * Returns {@code true} if this cursor is up-to-date with respect to the
0794: * relevant table and related table objects, {@code false} otherwise.
0795: */
0796: protected boolean isUpToDate() {
0797: return _rowState.isUpToDate();
0798: }
0799:
0800: @Override
0801: public String toString() {
0802: return getClass().getSimpleName() + " CurPosition " + _curPos
0803: + ", PrevPosition " + _prevPos;
0804: }
0805:
0806: /**
0807: * Finds the next non-deleted row after the given row (as defined by this
0808: * cursor) and returns the id of the row, where "next" may be backwards if
0809: * moveForward is {@code false}. If there are no more rows, the returned
0810: * rowId should equal the value returned by {@link #getLastPosition} if moving
0811: * forward and {@link #getFirstPosition} if moving backward.
0812: */
0813: protected abstract Position findAnotherPosition(RowState rowState,
0814: Position curPos, boolean moveForward) throws IOException;
0815:
0816: /**
0817: * Returns the DirHandler for the given movement direction.
0818: */
0819: protected abstract DirHandler getDirHandler(boolean moveForward);
0820:
0821: /**
0822: * Row iterator for this table, unmodifiable.
0823: */
0824: private final class RowIterator implements
0825: Iterator<Map<String, Object>> {
0826: private final Collection<String> _columnNames;
0827: private final boolean _moveForward;
0828: private boolean _hasNext = false;
0829:
0830: private RowIterator(Collection<String> columnNames,
0831: boolean moveForward) {
0832: try {
0833: _columnNames = columnNames;
0834: _moveForward = moveForward;
0835: reset(_moveForward);
0836: _hasNext = moveToAnotherRow(_moveForward);
0837: } catch (IOException e) {
0838: throw new IllegalStateException(e);
0839: }
0840: }
0841:
0842: public boolean hasNext() {
0843: return _hasNext;
0844: }
0845:
0846: public void remove() {
0847: throw new UnsupportedOperationException();
0848: }
0849:
0850: public Map<String, Object> next() {
0851: if (!hasNext()) {
0852: throw new NoSuchElementException();
0853: }
0854: try {
0855: Map<String, Object> rtn = getCurrentRow(_columnNames);
0856: _hasNext = moveToAnotherRow(_moveForward);
0857: return rtn;
0858: } catch (IOException e) {
0859: throw new IllegalStateException(e);
0860: }
0861: }
0862:
0863: }
0864:
0865: /**
0866: * Handles moving the cursor in a given direction. Separates cursor
0867: * logic from value storage.
0868: */
0869: protected abstract class DirHandler {
0870: public abstract Position getBeginningPosition();
0871:
0872: public abstract Position getEndPosition();
0873: }
0874:
0875: /**
0876: * Simple un-indexed cursor.
0877: */
0878: private static final class TableScanCursor extends Cursor {
0879: /** ScanDirHandler for forward traversal */
0880: private final ScanDirHandler _forwardDirHandler = new ForwardScanDirHandler();
0881: /** ScanDirHandler for backward traversal */
0882: private final ScanDirHandler _reverseDirHandler = new ReverseScanDirHandler();
0883: /** Cursor over the pages that this table owns */
0884: private final UsageMap.PageCursor _ownedPagesCursor;
0885:
0886: private TableScanCursor(Table table) {
0887: super (new Id(table, null), table, FIRST_SCAN_POSITION,
0888: LAST_SCAN_POSITION);
0889: _ownedPagesCursor = table.getOwnedPagesCursor();
0890: }
0891:
0892: @Override
0893: protected ScanDirHandler getDirHandler(boolean moveForward) {
0894: return (moveForward ? _forwardDirHandler
0895: : _reverseDirHandler);
0896: }
0897:
0898: @Override
0899: protected boolean isUpToDate() {
0900: return (super .isUpToDate() && _ownedPagesCursor
0901: .isUpToDate());
0902: }
0903:
0904: @Override
0905: protected void reset(boolean moveForward) {
0906: _ownedPagesCursor.reset(moveForward);
0907: super .reset(moveForward);
0908: }
0909:
0910: @Override
0911: protected void restorePositionImpl(Position curPos,
0912: Position prevPos) throws IOException {
0913: if (!(curPos instanceof ScanPosition)
0914: || !(prevPos instanceof ScanPosition)) {
0915: throw new IllegalArgumentException(
0916: "Restored positions must be scan positions");
0917: }
0918: _ownedPagesCursor.restorePosition(curPos.getRowId()
0919: .getPageNumber(), prevPos.getRowId()
0920: .getPageNumber());
0921: super .restorePositionImpl(curPos, prevPos);
0922: }
0923:
0924: @Override
0925: protected Position findAnotherPosition(RowState rowState,
0926: Position curPos, boolean moveForward)
0927: throws IOException {
0928: ScanDirHandler handler = getDirHandler(moveForward);
0929:
0930: // figure out how many rows are left on this page so we can find the
0931: // next row
0932: RowId curRowId = curPos.getRowId();
0933: Table.positionAtRowHeader(rowState, curRowId);
0934: int currentRowNumber = curRowId.getRowNumber();
0935:
0936: // loop until we find the next valid row or run out of pages
0937: while (true) {
0938:
0939: currentRowNumber = handler
0940: .getAnotherRowNumber(currentRowNumber);
0941: curRowId = new RowId(curRowId.getPageNumber(),
0942: currentRowNumber);
0943: Table.positionAtRowHeader(rowState, curRowId);
0944:
0945: if (!rowState.isValid()) {
0946:
0947: // load next page
0948: curRowId = new RowId(
0949: handler.getAnotherPageNumber(),
0950: RowId.INVALID_ROW_NUMBER);
0951: Table.positionAtRowHeader(rowState, curRowId);
0952:
0953: if (!rowState.isHeaderPageNumberValid()) {
0954: //No more owned pages. No more rows.
0955: return handler.getEndPosition();
0956: }
0957:
0958: // update row count and initial row number
0959: currentRowNumber = handler
0960: .getInitialRowNumber(rowState
0961: .getRowsOnHeaderPage());
0962:
0963: } else if (!rowState.isDeleted()) {
0964:
0965: // we found a valid, non-deleted row, return it
0966: return new ScanPosition(curRowId);
0967: }
0968:
0969: }
0970: }
0971:
0972: /**
0973: * Handles moving the table scan cursor in a given direction. Separates
0974: * cursor logic from value storage.
0975: */
0976: private abstract class ScanDirHandler extends DirHandler {
0977: public abstract int getAnotherRowNumber(int curRowNumber);
0978:
0979: public abstract int getAnotherPageNumber();
0980:
0981: public abstract int getInitialRowNumber(int rowsOnPage);
0982: }
0983:
0984: /**
0985: * Handles moving the table scan cursor forward.
0986: */
0987: private final class ForwardScanDirHandler extends
0988: ScanDirHandler {
0989: @Override
0990: public Position getBeginningPosition() {
0991: return getFirstPosition();
0992: }
0993:
0994: @Override
0995: public Position getEndPosition() {
0996: return getLastPosition();
0997: }
0998:
0999: @Override
1000: public int getAnotherRowNumber(int curRowNumber) {
1001: return curRowNumber + 1;
1002: }
1003:
1004: @Override
1005: public int getAnotherPageNumber() {
1006: return _ownedPagesCursor.getNextPage();
1007: }
1008:
1009: @Override
1010: public int getInitialRowNumber(int rowsOnPage) {
1011: return -1;
1012: }
1013: }
1014:
1015: /**
1016: * Handles moving the table scan cursor backward.
1017: */
1018: private final class ReverseScanDirHandler extends
1019: ScanDirHandler {
1020: @Override
1021: public Position getBeginningPosition() {
1022: return getLastPosition();
1023: }
1024:
1025: @Override
1026: public Position getEndPosition() {
1027: return getFirstPosition();
1028: }
1029:
1030: @Override
1031: public int getAnotherRowNumber(int curRowNumber) {
1032: return curRowNumber - 1;
1033: }
1034:
1035: @Override
1036: public int getAnotherPageNumber() {
1037: return _ownedPagesCursor.getPreviousPage();
1038: }
1039:
1040: @Override
1041: public int getInitialRowNumber(int rowsOnPage) {
1042: return rowsOnPage;
1043: }
1044: }
1045:
1046: }
1047:
1048: /**
1049: * Indexed cursor.
1050: */
1051: private static final class IndexCursor extends Cursor {
1052: /** IndexDirHandler for forward traversal */
1053: private final IndexDirHandler _forwardDirHandler = new ForwardIndexDirHandler();
1054: /** IndexDirHandler for backward traversal */
1055: private final IndexDirHandler _reverseDirHandler = new ReverseIndexDirHandler();
1056: /** Cursor over the entries of the relvant index */
1057: private final Index.EntryCursor _entryCursor;
1058:
1059: private IndexCursor(Table table, Index index,
1060: Index.EntryCursor entryCursor) throws IOException {
1061: super (new Id(table, index), table, new IndexPosition(
1062: entryCursor.getFirstEntry()), new IndexPosition(
1063: entryCursor.getLastEntry()));
1064: _entryCursor = entryCursor;
1065: }
1066:
1067: @Override
1068: public Index getIndex() {
1069: return _entryCursor.getIndex();
1070: }
1071:
1072: @Override
1073: protected IndexDirHandler getDirHandler(boolean moveForward) {
1074: return (moveForward ? _forwardDirHandler
1075: : _reverseDirHandler);
1076: }
1077:
1078: @Override
1079: protected boolean isUpToDate() {
1080: return (super .isUpToDate() && _entryCursor.isUpToDate());
1081: }
1082:
1083: @Override
1084: protected void reset(boolean moveForward) {
1085: _entryCursor.reset(moveForward);
1086: super .reset(moveForward);
1087: }
1088:
1089: @Override
1090: protected void restorePositionImpl(Position curPos,
1091: Position prevPos) throws IOException {
1092: if (!(curPos instanceof IndexPosition)
1093: || !(prevPos instanceof IndexPosition)) {
1094: throw new IllegalArgumentException(
1095: "Restored positions must be index positions");
1096: }
1097: _entryCursor.restorePosition(((IndexPosition) curPos)
1098: .getEntry(), ((IndexPosition) prevPos).getEntry());
1099: super .restorePositionImpl(curPos, prevPos);
1100: }
1101:
1102: @Override
1103: protected boolean findRowImpl(Column columnPattern,
1104: Object valuePattern) throws IOException {
1105: Object[] rowValues = _entryCursor.getIndex()
1106: .constructIndexRow(columnPattern.getName(),
1107: valuePattern);
1108:
1109: if (rowValues == null) {
1110: // bummer, use the default table scan
1111: return super .findRowImpl(columnPattern, valuePattern);
1112: }
1113:
1114: // sweet, we can use our index
1115: _entryCursor.beforeEntry(rowValues);
1116: Index.Entry startEntry = _entryCursor.getNextEntry();
1117: if (!startEntry.getRowId().isValid()) {
1118: // at end of index, no potential matches
1119: return false;
1120: }
1121:
1122: // either we found a row with the given value, or none exist in the
1123: // table
1124: restorePosition(new IndexPosition(startEntry));
1125: return currentRowMatches(columnPattern, valuePattern);
1126: }
1127:
1128: @Override
1129: protected boolean findRowImpl(Map<String, Object> rowPattern)
1130: throws IOException {
1131: Index index = _entryCursor.getIndex();
1132: Object[] rowValues = index.constructIndexRow(rowPattern);
1133:
1134: if (rowValues == null) {
1135: // bummer, use the default table scan
1136: return super .findRowImpl(rowPattern);
1137: }
1138:
1139: // sweet, we can use our index
1140: _entryCursor.beforeEntry(rowValues);
1141: Index.Entry startEntry = _entryCursor.getNextEntry();
1142: if (!startEntry.getRowId().isValid()) {
1143: // at end of index, no potential matches
1144: return false;
1145: }
1146: restorePosition(new IndexPosition(startEntry));
1147:
1148: Map<String, Object> indexRowPattern = null;
1149: if (rowPattern.size() == index.getColumns().size()) {
1150: // the rowPattern matches our index columns exactly, so we can
1151: // streamline our testing below
1152: indexRowPattern = rowPattern;
1153: } else {
1154: // the rowPattern has more columns than just the index, so we need to
1155: // do more work when testing below
1156: indexRowPattern = new LinkedHashMap<String, Object>();
1157: for (Index.ColumnDescriptor idxCol : index.getColumns()) {
1158: indexRowPattern.put(idxCol.getName(),
1159: rowValues[idxCol.getColumnIndex()]);
1160: }
1161: }
1162:
1163: // there may be multiple columns which fit the pattern subset used by
1164: // the index, so we need to keep checking until our index values no
1165: // longer match
1166: do {
1167:
1168: if (!currentRowMatches(indexRowPattern)) {
1169: // there are no more rows which could possibly match
1170: break;
1171: }
1172:
1173: // note, if rowPattern == indexRowPattern, no need to do an extra
1174: // comparison with the current row
1175: if ((rowPattern == indexRowPattern)
1176: || currentRowMatches(rowPattern)) {
1177: // found it!
1178: return true;
1179: }
1180:
1181: } while (moveToNextRow());
1182:
1183: // none of the potential rows matched
1184: return false;
1185: }
1186:
1187: @Override
1188: protected Position findAnotherPosition(RowState rowState,
1189: Position curPos, boolean moveForward)
1190: throws IOException {
1191: IndexDirHandler handler = getDirHandler(moveForward);
1192: IndexPosition endPos = (IndexPosition) handler
1193: .getEndPosition();
1194: Index.Entry entry = handler.getAnotherEntry();
1195: return ((!entry.equals(endPos.getEntry())) ? new IndexPosition(
1196: entry)
1197: : endPos);
1198: }
1199:
1200: /**
1201: * Handles moving the table index cursor in a given direction. Separates
1202: * cursor logic from value storage.
1203: */
1204: private abstract class IndexDirHandler extends DirHandler {
1205: public abstract Index.Entry getAnotherEntry();
1206: }
1207:
1208: /**
1209: * Handles moving the table index cursor forward.
1210: */
1211: private final class ForwardIndexDirHandler extends
1212: IndexDirHandler {
1213: @Override
1214: public Position getBeginningPosition() {
1215: return getFirstPosition();
1216: }
1217:
1218: @Override
1219: public Position getEndPosition() {
1220: return getLastPosition();
1221: }
1222:
1223: @Override
1224: public Index.Entry getAnotherEntry() {
1225: return _entryCursor.getNextEntry();
1226: }
1227: }
1228:
1229: /**
1230: * Handles moving the table index cursor backward.
1231: */
1232: private final class ReverseIndexDirHandler extends
1233: IndexDirHandler {
1234: @Override
1235: public Position getBeginningPosition() {
1236: return getLastPosition();
1237: }
1238:
1239: @Override
1240: public Position getEndPosition() {
1241: return getFirstPosition();
1242: }
1243:
1244: @Override
1245: public Index.Entry getAnotherEntry() {
1246: return _entryCursor.getPreviousEntry();
1247: }
1248: }
1249:
1250: }
1251:
1252: /**
1253: * Identifier for a cursor. Will be equal to any other cursor of the same
1254: * type for the same table. Primarily used to check the validity of a
1255: * Savepoint.
1256: */
1257: public static final class Id {
1258: private final String _tableName;
1259: private final String _indexName;
1260:
1261: private Id(Table table, Index index) {
1262: _tableName = table.getName();
1263: _indexName = ((index != null) ? index.getName() : null);
1264: }
1265:
1266: @Override
1267: public int hashCode() {
1268: return _tableName.hashCode();
1269: }
1270:
1271: @Override
1272: public boolean equals(Object o) {
1273: return ((this == o) || ((o != null)
1274: && (getClass() == o.getClass())
1275: && ObjectUtils.equals(_tableName,
1276: ((Id) o)._tableName) && ObjectUtils.equals(
1277: _indexName, ((Id) o)._indexName)));
1278: }
1279:
1280: @Override
1281: public String toString() {
1282: return getClass().getSimpleName() + " " + _tableName + ":"
1283: + _indexName;
1284: }
1285: }
1286:
1287: /**
1288: * Value object which represents a complete save state of the cursor.
1289: */
1290: public static final class Savepoint {
1291: private final Id _cursorId;
1292: private final Position _curPos;
1293: private final Position _prevPos;
1294:
1295: private Savepoint(Id cursorId, Position curPos, Position prevPos) {
1296: _cursorId = cursorId;
1297: _curPos = curPos;
1298: _prevPos = prevPos;
1299: }
1300:
1301: public Id getCursorId() {
1302: return _cursorId;
1303: }
1304:
1305: public Position getCurrentPosition() {
1306: return _curPos;
1307: }
1308:
1309: private Position getPreviousPosition() {
1310: return _prevPos;
1311: }
1312:
1313: @Override
1314: public String toString() {
1315: return getClass().getSimpleName() + " " + _cursorId
1316: + " CurPosition " + _curPos + ", PrevPosition "
1317: + _prevPos;
1318: }
1319: }
1320:
1321: /**
1322: * Value object which maintains the current position of the cursor.
1323: */
1324: public static abstract class Position {
1325: protected Position() {
1326: }
1327:
1328: @Override
1329: public final int hashCode() {
1330: return getRowId().hashCode();
1331: }
1332:
1333: @Override
1334: public final boolean equals(Object o) {
1335: return ((this == o) || ((o != null)
1336: && (getClass() == o.getClass()) && equalsImpl(o)));
1337: }
1338:
1339: /**
1340: * Returns the unique RowId of the position of the cursor.
1341: */
1342: public abstract RowId getRowId();
1343:
1344: /**
1345: * Returns {@code true} if the subclass specific info in a Position is
1346: * equal, {@code false} otherwise.
1347: * @param o object being tested for equality, guaranteed to be the same
1348: * class as this object
1349: */
1350: protected abstract boolean equalsImpl(Object o);
1351: }
1352:
1353: /**
1354: * Value object which maintains the current position of a TableScanCursor.
1355: */
1356: private static final class ScanPosition extends Position {
1357: private final RowId _rowId;
1358:
1359: private ScanPosition(RowId rowId) {
1360: _rowId = rowId;
1361: }
1362:
1363: @Override
1364: public RowId getRowId() {
1365: return _rowId;
1366: }
1367:
1368: @Override
1369: protected boolean equalsImpl(Object o) {
1370: return getRowId().equals(((ScanPosition) o).getRowId());
1371: }
1372:
1373: @Override
1374: public String toString() {
1375: return "RowId = " + getRowId();
1376: }
1377: }
1378:
1379: /**
1380: * Value object which maintains the current position of an IndexCursor.
1381: */
1382: private static final class IndexPosition extends Position {
1383: private final Index.Entry _entry;
1384:
1385: private IndexPosition(Index.Entry entry) {
1386: _entry = entry;
1387: }
1388:
1389: @Override
1390: public RowId getRowId() {
1391: return getEntry().getRowId();
1392: }
1393:
1394: public Index.Entry getEntry() {
1395: return _entry;
1396: }
1397:
1398: @Override
1399: protected boolean equalsImpl(Object o) {
1400: return getEntry().equals(((IndexPosition) o).getEntry());
1401: }
1402:
1403: @Override
1404: public String toString() {
1405: return "Entry = " + getEntry();
1406: }
1407: }
1408:
1409: }
|