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 47KB

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