You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Cursor.java 46KB

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