1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2000,2008 Oracle. All rights reserved. 5 * 6 * $Id: BlockIterator.java,v 12.7 2008/02/07 17:12:26 mark Exp $ 7 */ 8 9package com.sleepycat.collections; 10 11import java.util.ListIterator; 12import java.util.NoSuchElementException; 13 14import com.sleepycat.compat.DbCompat; 15import com.sleepycat.db.DatabaseEntry; 16import com.sleepycat.db.DatabaseException; 17import com.sleepycat.db.OperationStatus; 18import com.sleepycat.util.keyrange.KeyRange; 19 20/** 21 * An iterator that does not need closing because a cursor is not kept open 22 * across method calls. A cursor is opened to read a block of records at a 23 * time and then closed before the method returns. 24 * 25 * @author Mark Hayes 26 */ 27class BlockIterator implements BaseIterator { 28 29 private StoredCollection coll; 30 private boolean writeAllowed; 31 32 /** 33 * Slots for a block of record keys and values. The priKeys array is only 34 * used for secondary databases; otherwise it is set to the keys array. 35 */ 36 private byte[][] keys; 37 private byte[][] priKeys; 38 private byte[][] values; 39 40 /** 41 * The slot index of the record that would be returned by next(). 42 * nextIndex is always greater or equal to zero. If the next record is not 43 * available, then nextIndex is equal to keys.length or keys[nextIndex] is 44 * null. 45 * 46 * If the block is empty, then either the iterator is uninitialized or the 47 * key range is empty. Either way, nextIndex will be the array length and 48 * all array values will be null. This is the initial state set by the 49 * constructor. If remove() is used to delete all records in the key 50 * range, it will restore the iterator to this initial state. The block 51 * must never be allowed to become empty when the key range is non-empty, 52 * since then the iterator's position would be lost. [#15858] 53 */ 54 private int nextIndex; 55 56 /** 57 * The slot index of the record last returned by next() or previous(), or 58 * the record inserted by add(). dataIndex is -1 if the data record is not 59 * available. If greater or equal to zero, the slot at dataIndex is always 60 * non-null. 61 */ 62 private int dataIndex; 63 64 /** 65 * The iterator data last returned by next() or previous(). This value is 66 * set to null if dataIndex is -1, or if the state of the iterator is such 67 * that set() or remove() cannot be called. For example, after add() this 68 * field is set to null, even though the dataIndex is still valid. 69 */ 70 private Object dataObject; 71 72 /** 73 * Creates an iterator. 74 */ 75 BlockIterator(StoredCollection coll, boolean writeAllowed, int blockSize) { 76 77 this.coll = coll; 78 this.writeAllowed = writeAllowed; 79 80 keys = new byte[blockSize][]; 81 priKeys = coll.isSecondary() ? (new byte[blockSize][]) : keys; 82 values = new byte[blockSize][]; 83 84 nextIndex = blockSize; 85 dataIndex = -1; 86 dataObject = null; 87 } 88 89 /** 90 * Copy constructor. 91 */ 92 private BlockIterator(BlockIterator o) { 93 94 coll = o.coll; 95 writeAllowed = o.writeAllowed; 96 97 keys = copyArray(o.keys); 98 priKeys = coll.isSecondary() ? copyArray(o.priKeys) : keys; 99 values = copyArray(o.values); 100 101 nextIndex = o.nextIndex; 102 dataIndex = o.dataIndex; 103 dataObject = o.dataObject; 104 } 105 106 /** 107 * Copies an array of byte arrays. 108 */ 109 private byte[][] copyArray(byte[][] a) { 110 111 byte[][] b = new byte[a.length][]; 112 for (int i = 0; i < b.length; i += 1) { 113 if (a[i] != null) { 114 b[i] = KeyRange.copyBytes(a[i]); 115 } 116 } 117 return b; 118 } 119 120 /** 121 * Returns whether the element at nextIndex is available. 122 */ 123 private boolean isNextAvailable() { 124 125 return (nextIndex < keys.length) && 126 (keys[nextIndex] != null); 127 } 128 129 /** 130 * Returns whether the element at nextIndex-1 is available. 131 */ 132 private boolean isPrevAvailable() { 133 134 return (nextIndex > 0) && 135 (keys[nextIndex - 1] != null); 136 } 137 138 /** 139 * Returns the record number at the given slot position. 140 */ 141 private int getRecordNumber(int i) { 142 143 if (coll.view.btreeRecNumDb) { 144 DataCursor cursor = null; 145 try { 146 cursor = new DataCursor(coll.view, false); 147 if (moveCursor(i, cursor)) { 148 return cursor.getCurrentRecordNumber(); 149 } else { 150 throw new IllegalStateException(); 151 } 152 } catch (DatabaseException e) { 153 throw StoredContainer.convertException(e); 154 } finally { 155 closeCursor(cursor); 156 } 157 } else { 158 DatabaseEntry entry = new DatabaseEntry(keys[i]); 159 return DbCompat.getRecordNumber(entry); 160 } 161 } 162 163 /** 164 * Sets dataObject to the iterator data for the element at dataIndex. 165 */ 166 private void makeDataObject() { 167 168 int i = dataIndex; 169 DatabaseEntry keyEntry = new DatabaseEntry(keys[i]); 170 DatabaseEntry priKeyEntry = (keys != priKeys) 171 ? (new DatabaseEntry(priKeys[i])) 172 : keyEntry; 173 DatabaseEntry valuesEntry = new DatabaseEntry(values[i]); 174 175 dataObject = coll.makeIteratorData(this, keyEntry, priKeyEntry, 176 valuesEntry); 177 } 178 179 /** 180 * Sets all slots to null. 181 */ 182 private void clearSlots() { 183 184 for (int i = 0; i < keys.length; i += 1) { 185 keys[i] = null; 186 priKeys[i] = null; 187 values[i] = null; 188 } 189 } 190 191 /** 192 * Sets a given slot using the data in the given cursor. 193 */ 194 private void setSlot(int i, DataCursor cursor) { 195 196 keys[i] = KeyRange.getByteArray(cursor.getKeyThang()); 197 198 if (keys != priKeys) { 199 priKeys[i] = KeyRange.getByteArray 200 (cursor.getPrimaryKeyThang()); 201 } 202 203 values[i] = KeyRange.getByteArray(cursor.getValueThang()); 204 } 205 206 /** 207 * Inserts an added record at a given slot position and shifts other slots 208 * accordingly. Also adjusts nextIndex and sets dataIndex to -1. 209 */ 210 private void insertSlot(int i, DataCursor cursor) { 211 212 if (i < keys.length) { 213 for (int j = keys.length - 1; j > i; j -= 1) { 214 215 /* Shift right. */ 216 keys[j] = keys[j - 1]; 217 priKeys[j] = priKeys[j - 1]; 218 values[j] = values[j - 1]; 219 220 /* Bump key in recno-renumber database. */ 221 if (coll.view.recNumRenumber && keys[j] != null) { 222 bumpRecordNumber(j); 223 } 224 } 225 nextIndex += 1; 226 } else { 227 if (i != keys.length) { 228 throw new IllegalStateException(); 229 } 230 i -= 1; 231 for (int j = 0; j < i; j += 1) { 232 /* Shift left. */ 233 keys[j] = keys[j + 1]; 234 priKeys[j] = priKeys[j + 1]; 235 values[j] = values[j + 1]; 236 } 237 } 238 setSlot(i, cursor); 239 dataIndex = -1; 240 } 241 242 /** 243 * Increments the record number key at the given slot. 244 */ 245 private void bumpRecordNumber(int i) { 246 247 DatabaseEntry entry = new DatabaseEntry(keys[i]); 248 DbCompat.setRecordNumber(entry, 249 DbCompat.getRecordNumber(entry) + 1); 250 keys[i] = entry.getData(); 251 } 252 253 /** 254 * Deletes the given slot, adjusts nextIndex and sets dataIndex to -1. 255 */ 256 private void deleteSlot(int i) { 257 258 for (int j = i + 1; j < keys.length; j += 1) { 259 keys[j - 1] = keys[j]; 260 priKeys[j - 1] = priKeys[j]; 261 values[j - 1] = values[j]; 262 } 263 int last = keys.length - 1; 264 keys[last] = null; 265 priKeys[last] = null; 266 values[last] = null; 267 268 if (nextIndex > i) { 269 nextIndex -= 1; 270 } 271 dataIndex = -1; 272 } 273 274 /** 275 * Moves the cursor to the key/data at the given slot, and returns false 276 * if the reposition (search) fails. 277 */ 278 private boolean moveCursor(int i, DataCursor cursor) 279 throws DatabaseException { 280 281 return cursor.repositionExact(keys[i], priKeys[i], values[i], false); 282 } 283 284 /** 285 * Closes the given cursor if non-null. 286 */ 287 private void closeCursor(DataCursor cursor) { 288 289 if (cursor != null) { 290 try { 291 cursor.close(); 292 } catch (DatabaseException e) { 293 throw StoredContainer.convertException(e); 294 } 295 } 296 } 297 298 // --- begin Iterator/ListIterator methods --- 299 300 public boolean hasNext() { 301 302 if (isNextAvailable()) { 303 return true; 304 } 305 DataCursor cursor = null; 306 try { 307 cursor = new DataCursor(coll.view, writeAllowed); 308 int prev = nextIndex - 1; 309 boolean found = false; 310 311 if (keys[prev] == null) { 312 /* Get the first record for an uninitialized iterator. */ 313 OperationStatus status = cursor.getFirst(false); 314 if (status == OperationStatus.SUCCESS) { 315 found = true; 316 nextIndex = 0; 317 } 318 } else { 319 /* Reposition to the last known key/data pair. */ 320 int repos = cursor.repositionRange 321 (keys[prev], priKeys[prev], values[prev], false); 322 323 if (repos == DataCursor.REPOS_EXACT) { 324 325 /* 326 * The last known key/data pair was found and will now be 327 * in slot zero. 328 */ 329 found = true; 330 nextIndex = 1; 331 332 /* The data object is now in slot zero or not available. */ 333 if (dataIndex == prev) { 334 dataIndex = 0; 335 } else { 336 dataIndex = -1; 337 dataObject = null; 338 } 339 } else if (repos == DataCursor.REPOS_NEXT) { 340 341 /* 342 * The last known key/data pair was not found, but the 343 * following record was found and it will be in slot zero. 344 */ 345 found = true; 346 nextIndex = 0; 347 348 /* The data object is no longer available. */ 349 dataIndex = -1; 350 dataObject = null; 351 } else { 352 if (repos != DataCursor.REPOS_EOF) { 353 throw new IllegalStateException(); 354 } 355 } 356 } 357 358 if (found) { 359 /* Clear all slots first in case an exception occurs below. */ 360 clearSlots(); 361 362 /* Attempt to fill all slots with records. */ 363 int i = 0; 364 boolean done = false; 365 while (!done) { 366 setSlot(i, cursor); 367 i += 1; 368 if (i < keys.length) { 369 OperationStatus status = coll.iterateDuplicates() ? 370 cursor.getNext(false) : 371 cursor.getNextNoDup(false); 372 if (status != OperationStatus.SUCCESS) { 373 done = true; 374 } 375 } else { 376 done = true; 377 } 378 } 379 380 } 381 382 /* 383 * If REPOS_EXACT was returned above, make sure we retrieved 384 * the following record. 385 */ 386 return isNextAvailable(); 387 } catch (DatabaseException e) { 388 throw StoredContainer.convertException(e); 389 } finally { 390 closeCursor(cursor); 391 } 392 } 393 394 public boolean hasPrevious() { 395 396 if (isPrevAvailable()) { 397 return true; 398 } 399 if (!isNextAvailable()) { 400 return false; 401 } 402 DataCursor cursor = null; 403 try { 404 cursor = new DataCursor(coll.view, writeAllowed); 405 int last = keys.length - 1; 406 int next = nextIndex; 407 boolean found = false; 408 409 /* Reposition to the last known key/data pair. */ 410 int repos = cursor.repositionRange 411 (keys[next], priKeys[next], values[next], false); 412 413 if (repos == DataCursor.REPOS_EXACT || 414 repos == DataCursor.REPOS_NEXT) { 415 416 /* 417 * The last known key/data pair, or the record following it, 418 * was found and will now be in the last slot. 419 */ 420 found = true; 421 nextIndex = last; 422 423 /* The data object is now in the last slot or not available. */ 424 if (dataIndex == next && repos == DataCursor.REPOS_EXACT) { 425 dataIndex = last; 426 } else { 427 dataIndex = -1; 428 dataObject = null; 429 } 430 } else { 431 if (repos != DataCursor.REPOS_EOF) { 432 throw new IllegalStateException(); 433 } 434 } 435 436 if (found) { 437 /* Clear all slots first in case an exception occurs below. */ 438 clearSlots(); 439 440 /* Attempt to fill all slots with records. */ 441 int i = last; 442 boolean done = false; 443 while (!done) { 444 setSlot(i, cursor); 445 i -= 1; 446 if (i >= 0) { 447 OperationStatus status = coll.iterateDuplicates() ? 448 cursor.getPrev(false) : 449 cursor.getPrevNoDup(false); 450 if (status != OperationStatus.SUCCESS) { 451 done = true; 452 } 453 } else { 454 done = true; 455 } 456 } 457 } 458 459 /* 460 * Make sure we retrieved the preceding record after the reposition 461 * above. 462 */ 463 return isPrevAvailable(); 464 } catch (DatabaseException e) { 465 throw StoredContainer.convertException(e); 466 } finally { 467 closeCursor(cursor); 468 } 469 } 470 471 public Object next() { 472 473 if (hasNext()) { 474 dataIndex = nextIndex; 475 nextIndex += 1; 476 makeDataObject(); 477 return dataObject; 478 } else { 479 throw new NoSuchElementException(); 480 } 481 } 482 483 public Object previous() { 484 485 if (hasPrevious()) { 486 nextIndex -= 1; 487 dataIndex = nextIndex; 488 makeDataObject(); 489 return dataObject; 490 } else { 491 throw new NoSuchElementException(); 492 } 493 } 494 495 public int nextIndex() { 496 497 if (!coll.view.recNumAccess) { 498 throw new UnsupportedOperationException( 499 "Record number access not supported"); 500 } 501 502 return hasNext() ? (getRecordNumber(nextIndex) - 503 coll.getIndexOffset()) 504 : Integer.MAX_VALUE; 505 } 506 507 public int previousIndex() { 508 509 if (!coll.view.recNumAccess) { 510 throw new UnsupportedOperationException( 511 "Record number access not supported"); 512 } 513 514 return hasPrevious() ? (getRecordNumber(nextIndex - 1) - 515 coll.getIndexOffset()) 516 : (-1); 517 } 518 519 public void set(Object value) { 520 521 if (dataObject == null) { 522 throw new IllegalStateException(); 523 } 524 if (!coll.hasValues()) { 525 throw new UnsupportedOperationException(); 526 } 527 DataCursor cursor = null; 528 boolean doAutoCommit = coll.beginAutoCommit(); 529 try { 530 cursor = new DataCursor(coll.view, writeAllowed); 531 if (moveCursor(dataIndex, cursor)) { 532 cursor.putCurrent(value); 533 setSlot(dataIndex, cursor); 534 coll.closeCursor(cursor); 535 coll.commitAutoCommit(doAutoCommit); 536 } else { 537 throw new IllegalStateException(); 538 } 539 } catch (Exception e) { 540 coll.closeCursor(cursor); 541 throw coll.handleException(e, doAutoCommit); 542 } 543 } 544 545 public void remove() { 546 547 if (dataObject == null) { 548 throw new IllegalStateException(); 549 } 550 DataCursor cursor = null; 551 boolean doAutoCommit = coll.beginAutoCommit(); 552 try { 553 cursor = new DataCursor(coll.view, writeAllowed); 554 if (moveCursor(dataIndex, cursor)) { 555 cursor.delete(); 556 deleteSlot(dataIndex); 557 dataObject = null; 558 559 /* 560 * Repopulate the block after removing all records, using the 561 * cursor position of the deleted record as a starting point. 562 * First try moving forward, since the user was moving forward. 563 * (It is possible to delete all records in the block only by 564 * moving forward, i.e, when nextIndex is greater than 565 * dataIndex.) 566 */ 567 if (nextIndex == 0 && keys[0] == null) { 568 OperationStatus status; 569 for (int i = 0; i < keys.length; i += 1) { 570 status = coll.iterateDuplicates() ? 571 cursor.getNext(false) : 572 cursor.getNextNoDup(false); 573 if (status == OperationStatus.SUCCESS) { 574 setSlot(i, cursor); 575 } else { 576 break; 577 } 578 } 579 580 /* 581 * If no records are found past the cursor position, try 582 * moving backward. If no records are found before the 583 * cursor position, leave nextIndex set to keys.length, 584 * which is the same as the initial iterator state and is 585 * appropriate for an empty key range. 586 */ 587 if (keys[0] == null) { 588 nextIndex = keys.length; 589 for (int i = nextIndex - 1; i >= 0; i -= 1) { 590 status = coll.iterateDuplicates() ? 591 cursor.getPrev(false) : 592 cursor.getPrevNoDup(false); 593 if (status == OperationStatus.SUCCESS) { 594 setSlot(i, cursor); 595 } else { 596 break; 597 } 598 } 599 } 600 } 601 coll.closeCursor(cursor); 602 coll.commitAutoCommit(doAutoCommit); 603 } else { 604 throw new IllegalStateException(); 605 } 606 } catch (Exception e) { 607 coll.closeCursor(cursor); 608 throw coll.handleException(e, doAutoCommit); 609 } 610 } 611 612 public void add(Object value) { 613 614 /* 615 * The checkIterAddAllowed method ensures that one of the following two 616 * conditions holds and throws UnsupportedOperationException otherwise: 617 * 1- This is a list iterator for a recno-renumber database. 618 * 2- This is a collection iterator for a duplicates database. 619 */ 620 coll.checkIterAddAllowed(); 621 OperationStatus status = OperationStatus.SUCCESS; 622 DataCursor cursor = null; 623 boolean doAutoCommit = coll.beginAutoCommit(); 624 try { 625 if (coll.view.keysRenumbered || !coll.areDuplicatesOrdered()) { 626 627 /* 628 * This is a recno-renumber database or a btree/hash database 629 * with unordered duplicates. 630 */ 631 boolean hasPrev = hasPrevious(); 632 if (!hasPrev && !hasNext()) { 633 634 /* The collection is empty. */ 635 if (coll.view.keysRenumbered) { 636 637 /* Append to an empty recno-renumber database. */ 638 status = coll.view.append(value, null, null); 639 640 } else if (coll.view.dupsAllowed && 641 coll.view.range.isSingleKey()) { 642 643 /* 644 * When inserting a duplicate into a single-key range, 645 * the main key is fixed, so we can always insert into 646 * an empty collection. 647 */ 648 cursor = new DataCursor(coll.view, writeAllowed); 649 cursor.useRangeKey(); 650 status = cursor.putNoDupData(null, value, null, true); 651 coll.closeCursor(cursor); 652 cursor = null; 653 } else { 654 throw new IllegalStateException 655 ("Collection is empty, cannot add() duplicate"); 656 } 657 658 /* 659 * Move past the record just inserted (the cursor should be 660 * closed above to prevent multiple open cursors in certain 661 * DB core modes). 662 */ 663 if (status == OperationStatus.SUCCESS) { 664 next(); 665 dataIndex = nextIndex - 1; 666 } 667 } else { 668 669 /* 670 * The collection is non-empty. If hasPrev is true then 671 * the element at (nextIndex - 1) is available; otherwise 672 * the element at nextIndex is available. 673 */ 674 cursor = new DataCursor(coll.view, writeAllowed); 675 int insertIndex = hasPrev ? (nextIndex - 1) : nextIndex; 676 677 if (!moveCursor(insertIndex, cursor)) { 678 throw new IllegalStateException(); 679 } 680 681 /* 682 * For a recno-renumber database or a database with 683 * unsorted duplicates, insert before the iterator 'next' 684 * position, or after the 'prev' position. Then adjust 685 * the slots to account for the inserted record. 686 */ 687 status = hasPrev ? cursor.putAfter(value) 688 : cursor.putBefore(value); 689 if (status == OperationStatus.SUCCESS) { 690 insertSlot(nextIndex, cursor); 691 } 692 } 693 } else { 694 /* This is a btree/hash database with ordered duplicates. */ 695 cursor = new DataCursor(coll.view, writeAllowed); 696 697 if (coll.view.range.isSingleKey()) { 698 699 /* 700 * When inserting a duplicate into a single-key range, 701 * the main key is fixed. 702 */ 703 cursor.useRangeKey(); 704 } else { 705 706 /* 707 * When inserting into a multi-key range, the main key 708 * is the last dataIndex accessed by next(), previous() 709 * or add(). 710 */ 711 if (dataIndex < 0 || !moveCursor(dataIndex, cursor)) { 712 throw new IllegalStateException(); 713 } 714 } 715 716 /* 717 * For a hash/btree with duplicates, insert the duplicate, 718 * put the new record in slot zero, and set the next index 719 * to slot one (past the new record). 720 */ 721 status = cursor.putNoDupData(null, value, null, true); 722 if (status == OperationStatus.SUCCESS) { 723 clearSlots(); 724 setSlot(0, cursor); 725 dataIndex = 0; 726 nextIndex = 1; 727 } 728 } 729 730 if (status == OperationStatus.KEYEXIST) { 731 throw new IllegalArgumentException("Duplicate value"); 732 } else if (status != OperationStatus.SUCCESS) { 733 throw new IllegalArgumentException("Could not insert: " + 734 status); 735 } 736 737 /* Prevent subsequent set() or remove() call. */ 738 dataObject = null; 739 740 coll.closeCursor(cursor); 741 coll.commitAutoCommit(doAutoCommit); 742 } catch (Exception e) { 743 coll.closeCursor(cursor); 744 throw coll.handleException(e, doAutoCommit); 745 } 746 } 747 748 // --- end Iterator/ListIterator methods --- 749 750 // --- begin BaseIterator methods --- 751 752 public final ListIterator dup() { 753 754 return new BlockIterator(this); 755 } 756 757 public final boolean isCurrentData(Object currentData) { 758 759 return (dataObject == currentData); 760 } 761 762 public final boolean moveToIndex(int index) { 763 764 DataCursor cursor = null; 765 try { 766 cursor = new DataCursor(coll.view, writeAllowed); 767 OperationStatus status = 768 cursor.getSearchKey(new Integer(index), null, false); 769 if (status == OperationStatus.SUCCESS) { 770 clearSlots(); 771 setSlot(0, cursor); 772 nextIndex = 0; 773 return true; 774 } else { 775 return false; 776 } 777 } catch (DatabaseException e) { 778 throw StoredContainer.convertException(e); 779 } finally { 780 closeCursor(cursor); 781 } 782 } 783 784 // --- end BaseIterator methods --- 785} 786