Coverage Report - com.healthmarketscience.jackcess.Cursor
 
Classes in this File Line Coverage Branch Coverage Complexity
Cursor
92%
134/145
90%
43/48
0
Cursor$1
100%
2/2
N/A
0
Cursor$2
100%
2/2
N/A
0
Cursor$DirHandler
100%
1/1
N/A
0
Cursor$Id
88%
7/8
75%
9/12
0
Cursor$IndexCursor
98%
50/51
91%
29/32
0
Cursor$IndexCursor$ForwardIndexDirHandler
100%
4/4
N/A
0
Cursor$IndexCursor$IndexDirHandler
100%
1/1
N/A
0
Cursor$IndexCursor$ReverseIndexDirHandler
100%
4/4
N/A
0
Cursor$IndexPosition
100%
8/8
N/A
0
Cursor$Position
75%
3/4
75%
6/8
0
Cursor$RowIterator
70%
14/20
50%
1/2
0
Cursor$Savepoint
100%
10/10
N/A
0
Cursor$ScanPosition
100%
7/7
N/A
0
Cursor$TableScanCursor
97%
30/31
81%
13/16
0
Cursor$TableScanCursor$ForwardScanDirHandler
100%
6/6
N/A
0
Cursor$TableScanCursor$ReverseScanDirHandler
100%
6/6
N/A
0
Cursor$TableScanCursor$ScanDirHandler
100%
1/1
N/A
0
 
 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  17344
 public abstract class Cursor implements Iterable<Map<String, Object>>
 60  
 {  
 61  1
   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  1
   private static final ScanPosition FIRST_SCAN_POSITION =
 70  
     new ScanPosition(RowId.FIRST_ROW_ID);
 71  
   /** last position for the TableScanCursor */
 72  1
   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  2324
   protected Cursor(Id id, Table table, Position firstPos, Position lastPos) {
 92  2324
     _id = id;
 93  2324
     _table = table;
 94  2324
     _rowState = _table.createRowState();
 95  2324
     _firstPos = firstPos;
 96  2324
     _lastPos = lastPos;
 97  2324
     _curPos = firstPos;
 98  2324
     _prevPos = firstPos;
 99  2324
   }
 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  383
     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  88
     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  1877
     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  1941
     if(table != index.getTable()) {
 175  0
       throw new IllegalArgumentException(
 176  
           "Given index is not for given table: " + index + ", " + table);
 177  
     }
 178  1941
     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  4
     Cursor cursor = createCursor(table);
 197  4
     if(cursor.findRow(rowPattern)) {
 198  4
       return cursor.getCurrentRow();
 199  
     }
 200  0
     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  4
     Cursor cursor = createCursor(table);
 225  4
     if(cursor.findRow(columnPattern, valuePattern)) {
 226  4
       return cursor.getCurrentRowValue(column);
 227  
     }
 228  0
     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  6
     Cursor cursor = createIndexCursor(table, index);
 246  6
     if(cursor.findRow(rowPattern)) {
 247  3
       return cursor.getCurrentRow();
 248  
     }
 249  3
     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  6
     Cursor cursor = createIndexCursor(table, index);
 275  6
     if(cursor.findRow(columnPattern, valuePattern)) {
 276  3
       return cursor.getCurrentRowValue(column);
 277  
     }
 278  3
     return null;
 279  
   }
 280  
   
 281  
   public Id getId() {
 282  0
     return _id;
 283  
   }
 284  
 
 285  
   public Table getTable() {
 286  20
     return _table;
 287  
   }
 288  
 
 289  
   public Index getIndex() {
 290  8
     return null;
 291  
   }
 292  
   
 293  
   public JetFormat getFormat() {
 294  0
     return getTable().getFormat();
 295  
   }
 296  
 
 297  
   public PageChannel getPageChannel() {
 298  0
     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  5424
     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  12
     if(!_id.equals(savepoint.getCursorId())) {
 322  2
       throw new IllegalArgumentException(
 323  
           "Savepoint " + savepoint + " is not valid for this cursor with id "
 324  
           + _id);
 325  
     }
 326  10
     restorePosition(savepoint.getCurrentPosition(),
 327  
                     savepoint.getPreviousPosition());
 328  10
   }
 329  
   
 330  
   /**
 331  
    * Returns the first row id (exclusive) as defined by this cursor.
 332  
    */
 333  
   protected Position getFirstPosition() {
 334  8187
     return _firstPos;
 335  
   }
 336  
   
 337  
   /**
 338  
    * Returns the last row id (exclusive) as defined by this cursor.
 339  
    */
 340  
   protected Position getLastPosition() {
 341  55459
     return _lastPos;
 342  
   }
 343  
 
 344  
   /**
 345  
    * Resets this cursor for forward traversal.  Calls {@link #beforeFirst}.
 346  
    */
 347  
   public void reset() {
 348  27
     beforeFirst();
 349  27
   }  
 350  
 
 351  
   /**
 352  
    * Resets this cursor for forward traversal (sets cursor to before the first
 353  
    * row).
 354  
    */
 355  
   public void beforeFirst() {
 356  1860
     reset(MOVE_FORWARD);
 357  1860
   }
 358  
   
 359  
   /**
 360  
    * Resets this cursor for reverse traversal (sets cursor to after the last
 361  
    * row).
 362  
    */
 363  
   public void afterLast() {
 364  11
     reset(MOVE_REVERSE);
 365  11
   }
 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  36
     if(getFirstPosition().equals(_curPos)) {
 375  12
       return !recheckPosition(MOVE_REVERSE);
 376  
     }
 377  24
     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  64
     if(getLastPosition().equals(_curPos)) {
 388  40
       return !recheckPosition(MOVE_FORWARD);
 389  
     }
 390  24
     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  32
     Table.positionAtRowData(_rowState, _curPos.getRowId());
 403  32
     return _rowState.isDeleted();
 404  
   }
 405  
   
 406  
   /**
 407  
    * Resets this cursor for traversing the given direction.
 408  
    */
 409  
   protected void reset(boolean moveForward) {
 410  2007
     _curPos = getDirHandler(moveForward).getBeginningPosition();
 411  2007
     _prevPos = _curPos;
 412  2007
     _rowState.reset();
 413  2007
   }  
 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  4
     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  4
     return new Iterable<Map<String, Object>>() {
 440  4
       public Iterator<Map<String, Object>> iterator() {
 441  4
         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  60
     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  67
     return new Iterable<Map<String, Object>>() {
 469  67
       public Iterator<Map<String, Object>> iterator() {
 470  67
         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  132
     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  2092
     _table.deleteRow(_rowState, _curPos.getRowId());
 495  2092
   }
 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  20
     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  2081
     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  4
     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  4
     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  2085
     if(moveToAnotherRow(moveForward)) {
 553  2080
       return getCurrentRow(columnNames);
 554  
     }
 555  5
     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  5209
     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  2010
     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