1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2000,2008 Oracle. All rights reserved. 5 * 6 * $Id: StoredIterator.java,v 12.9 2008/02/07 17:12:26 mark Exp $ 7 */ 8 9package com.sleepycat.collections; 10 11import java.util.Iterator; 12import java.util.ListIterator; 13import java.util.NoSuchElementException; 14 15import com.sleepycat.db.DatabaseException; 16import com.sleepycat.db.OperationStatus; 17import com.sleepycat.util.RuntimeExceptionWrapper; 18 19/** 20 * The Iterator returned by all stored collections. 21 * 22 * <p>While in general this class conforms to the {@link Iterator} interface, 23 * it is important to note that all iterators for stored collections must be 24 * explicitly closed with {@link #close()}. The static method {@link 25 * #close(java.util.Iterator)} allows calling close for all iterators without 26 * harm to iterators that are not from stored collections, and also avoids 27 * casting. If a stored iterator is not closed, unpredictable behavior 28 * including process death may result.</p> 29 * 30 * <p>This class implements the {@link Iterator} interface for all stored 31 * iterators. It also implements {@link ListIterator} because some list 32 * iterator methods apply to all stored iterators, for example, {@link 33 * #previous} and {@link #hasPrevious}. Other list iterator methods are always 34 * supported for lists, but for other types of collections are only supported 35 * under certain conditions. See {@link #nextIndex}, {@link #previousIndex}, 36 * {@link #add} and {@link #set} for details.</p> 37 * 38 * <p>In addition, this class provides the following methods for stored 39 * collection iterators only. Note that the use of these methods is not 40 * compatible with the standard Java collections interface.</p> 41 * <ul> 42 * <li>{@link #close()}</li> 43 * <li>{@link #close(Iterator)}</li> 44 * <li>{@link #count()}</li> 45 * <li>{@link #getCollection}</li> 46 * <li>{@link #setReadModifyWrite}</li> 47 * <li>{@link #isReadModifyWrite}</li> 48 * </ul> 49 * 50 * @author Mark Hayes 51 */ 52public class StoredIterator implements BaseIterator, Cloneable { 53 54 /** 55 * Closes the given iterator using {@link #close()} if it is a {@link 56 * StoredIterator}. If the given iterator is not a {@link StoredIterator}, 57 * this method does nothing. 58 * 59 * @param i is the iterator to close. 60 * 61 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is thrown. 62 */ 63 public static void close(Iterator i) { 64 65 if (i instanceof StoredIterator) { 66 ((StoredIterator) i).close(); 67 } 68 } 69 70 private static final int MOVE_NEXT = 1; 71 private static final int MOVE_PREV = 2; 72 private static final int MOVE_FIRST = 3; 73 74 private boolean lockForWrite; 75 private StoredCollection coll; 76 private DataCursor cursor; 77 private int toNext; 78 private int toPrevious; 79 private int toCurrent; 80 private boolean writeAllowed; 81 private boolean setAndRemoveAllowed; 82 private Object currentData; 83 84 StoredIterator(StoredCollection coll, boolean writeAllowed, 85 DataCursor joinCursor) { 86 try { 87 this.coll = coll; 88 this.writeAllowed = writeAllowed; 89 if (joinCursor == null) 90 this.cursor = new DataCursor(coll.view, writeAllowed); 91 else 92 this.cursor = joinCursor; 93 reset(); 94 } catch (Exception e) { 95 try { 96 /* Ensure that the cursor is closed. [#10516] */ 97 close(); 98 } catch (Exception ignored) { 99 /* Klockwork - ok */ 100 } 101 throw StoredContainer.convertException(e); 102 } 103 } 104 105 /** 106 * Returns whether write-locks will be obtained when reading with this 107 * cursor. 108 * Obtaining write-locks can prevent deadlocks when reading and then 109 * modifying data. 110 * 111 * @return the write-lock setting. 112 */ 113 public final boolean isReadModifyWrite() { 114 115 return lockForWrite; 116 } 117 118 /** 119 * Changes whether write-locks will be obtained when reading with this 120 * cursor. 121 * Obtaining write-locks can prevent deadlocks when reading and then 122 * modifying data. 123 * 124 * @param lockForWrite the write-lock setting. 125 */ 126 public void setReadModifyWrite(boolean lockForWrite) { 127 128 this.lockForWrite = lockForWrite; 129 } 130 131 // --- begin Iterator/ListIterator methods --- 132 133 /** 134 * Returns true if this iterator has more elements when traversing in the 135 * forward direction. False is returned if the iterator has been closed. 136 * This method conforms to the {@link Iterator#hasNext} interface. 137 * 138 * @return whether {@link #next()} will succeed. 139 * 140 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is thrown. 141 */ 142 public boolean hasNext() { 143 144 if (cursor == null) { 145 return false; 146 } 147 try { 148 if (toNext != 0) { 149 OperationStatus status = move(toNext); 150 if (status == OperationStatus.SUCCESS) { 151 toNext = 0; 152 toPrevious = MOVE_PREV; 153 toCurrent = MOVE_PREV; 154 } 155 } 156 return (toNext == 0); 157 } catch (Exception e) { 158 throw StoredContainer.convertException(e); 159 } 160 } 161 162 /** 163 * Returns true if this iterator has more elements when traversing in the 164 * reverse direction. It returns false if the iterator has been closed. 165 * This method conforms to the {@link ListIterator#hasPrevious} interface. 166 * 167 * @return whether {@link #previous()} will succeed. 168 * 169 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is thrown. 170 */ 171 public boolean hasPrevious() { 172 173 if (cursor == null) { 174 return false; 175 } 176 try { 177 if (toPrevious != 0) { 178 OperationStatus status = move(toPrevious); 179 if (status == OperationStatus.SUCCESS) { 180 toPrevious = 0; 181 toNext = MOVE_NEXT; 182 toCurrent = MOVE_NEXT; 183 } 184 } 185 return (toPrevious == 0); 186 } catch (Exception e) { 187 throw StoredContainer.convertException(e); 188 } 189 } 190 191 /** 192 * Returns the next element in the iteration. 193 * This method conforms to the {@link Iterator#next} interface. 194 * 195 * @return the next element. 196 * 197 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 198 * thrown. 199 */ 200 public Object next() { 201 202 try { 203 if (toNext != 0) { 204 OperationStatus status = move(toNext); 205 if (status == OperationStatus.SUCCESS) { 206 toNext = 0; 207 } 208 } 209 if (toNext == 0) { 210 currentData = coll.makeIteratorData(this, cursor); 211 toNext = MOVE_NEXT; 212 toPrevious = 0; 213 toCurrent = 0; 214 setAndRemoveAllowed = true; 215 return currentData; 216 } 217 // else throw NoSuchElementException below 218 } catch (Exception e) { 219 throw StoredContainer.convertException(e); 220 } 221 throw new NoSuchElementException(); 222 } 223 224 /** 225 * Returns the next element in the iteration. 226 * This method conforms to the {@link ListIterator#previous} interface. 227 * 228 * @return the previous element. 229 * 230 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 231 * thrown. 232 */ 233 public Object previous() { 234 235 try { 236 if (toPrevious != 0) { 237 OperationStatus status = move(toPrevious); 238 if (status == OperationStatus.SUCCESS) { 239 toPrevious = 0; 240 } 241 } 242 if (toPrevious == 0) { 243 currentData = coll.makeIteratorData(this, cursor); 244 toPrevious = MOVE_PREV; 245 toNext = 0; 246 toCurrent = 0; 247 setAndRemoveAllowed = true; 248 return currentData; 249 } 250 // else throw NoSuchElementException below 251 } catch (Exception e) { 252 throw StoredContainer.convertException(e); 253 } 254 throw new NoSuchElementException(); 255 } 256 257 /** 258 * Returns the index of the element that would be returned by a subsequent 259 * call to next. 260 * This method conforms to the {@link ListIterator#nextIndex} interface 261 * except that it returns Integer.MAX_VALUE for stored lists when 262 * positioned at the end of the list, rather than returning the list size 263 * as specified by the ListIterator interface. This is because the database 264 * size is not available. 265 * 266 * @return the next index. 267 * 268 * @throws UnsupportedOperationException if this iterator's collection does 269 * not use record number keys. 270 * 271 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 272 * thrown. 273 */ 274 public int nextIndex() { 275 276 if (!coll.view.recNumAccess) { 277 throw new UnsupportedOperationException( 278 "Record number access not supported"); 279 } 280 try { 281 return hasNext() ? (cursor.getCurrentRecordNumber() - 282 coll.getIndexOffset()) 283 : Integer.MAX_VALUE; 284 } catch (Exception e) { 285 throw StoredContainer.convertException(e); 286 } 287 } 288 289 /** 290 * Returns the index of the element that would be returned by a subsequent 291 * call to previous. 292 * This method conforms to the {@link ListIterator#previousIndex} 293 * interface. 294 * 295 * @return the previous index. 296 * 297 * @throws UnsupportedOperationException if this iterator's collection does 298 * not use record number keys. 299 * 300 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 301 * thrown. 302 */ 303 public int previousIndex() { 304 305 if (!coll.view.recNumAccess) { 306 throw new UnsupportedOperationException( 307 "Record number access not supported"); 308 } 309 try { 310 return hasPrevious() ? (cursor.getCurrentRecordNumber() - 311 coll.getIndexOffset()) 312 : (-1); 313 } catch (Exception e) { 314 throw StoredContainer.convertException(e); 315 } 316 } 317 318 /** 319 * Replaces the last element returned by next or previous with the 320 * specified element (optional operation). 321 * This method conforms to the {@link ListIterator#set} interface. 322 * 323 * <p>In order to call this method, if the underlying Database is 324 * transactional then a transaction must be active when creating the 325 * iterator.</p> 326 * 327 * @param value the new value. 328 * 329 * @throws UnsupportedOperationException if the collection is a {@link 330 * StoredKeySet} (the set returned by {@link java.util.Map#keySet}), or if 331 * duplicates are sorted since this would change the iterator position, or 332 * if the collection is indexed, or if the collection is read-only. 333 * 334 * @throws IllegalArgumentException if an entity value binding is used and 335 * the primary key of the value given is different than the existing stored 336 * primary key. 337 * 338 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 339 * thrown. 340 */ 341 public void set(Object value) { 342 343 if (!coll.hasValues()) throw new UnsupportedOperationException(); 344 if (!setAndRemoveAllowed) throw new IllegalStateException(); 345 try { 346 moveToCurrent(); 347 cursor.putCurrent(value); 348 } catch (Exception e) { 349 throw StoredContainer.convertException(e); 350 } 351 } 352 353 /** 354 * Removes the last element that was returned by next or previous (optional 355 * operation). 356 * This method conforms to the {@link ListIterator#remove} interface except 357 * that when the collection is a list and the RECNO-RENUMBER access method 358 * is not used, list indices will not be renumbered. 359 * 360 * <p>In order to call this method, if the underlying Database is 361 * transactional then a transaction must be active when creating the 362 * iterator.</p> 363 * 364 * <p>Note that for the JE product, RECNO-RENUMBER databases are not 365 * supported, and therefore list indices are never renumbered by this 366 * method.</p> 367 * 368 * @throws UnsupportedOperationException if the collection is a sublist, or 369 * if the collection is read-only. 370 * 371 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 372 * thrown. 373 */ 374 public void remove() { 375 376 if (!setAndRemoveAllowed) throw new IllegalStateException(); 377 try { 378 moveToCurrent(); 379 cursor.delete(); 380 setAndRemoveAllowed = false; 381 toNext = MOVE_NEXT; 382 toPrevious = MOVE_PREV; 383 } catch (Exception e) { 384 throw StoredContainer.convertException(e); 385 } 386 } 387 388 /** 389 * Inserts the specified element into the list or inserts a duplicate into 390 * other types of collections (optional operation). 391 * This method conforms to the {@link ListIterator#add} interface when 392 * the collection is a list and the RECNO-RENUMBER access method is used. 393 * Otherwise, this method may only be called when duplicates are allowed. 394 * If duplicates are unsorted, the new value will be inserted in the same 395 * manner as list elements. 396 * If duplicates are sorted, the new value will be inserted in sort order. 397 * 398 * <p>Note that for the JE product, RECNO-RENUMBER databases are not 399 * supported, and therefore this method may only be used to add 400 * duplicates.</p> 401 * 402 * @param value the new value. 403 * 404 * @throws UnsupportedOperationException if the collection is a sublist, or 405 * if the collection is indexed, or if the collection is read-only, or if 406 * the collection is a list and the RECNO-RENUMBER access method was not 407 * used, or if the collection is not a list and duplicates are not allowed. 408 * 409 * @throws IllegalStateException if the collection is empty and is not a 410 * list with RECNO-RENUMBER access. 411 * 412 * @throws IllegalArgumentException if a duplicate value is being added 413 * that already exists and duplicates are sorted. 414 * 415 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 416 * thrown. 417 */ 418 public void add(Object value) { 419 420 coll.checkIterAddAllowed(); 421 try { 422 OperationStatus status = OperationStatus.SUCCESS; 423 if (toNext != 0 && toPrevious != 0) { // database is empty 424 if (coll.view.keysRenumbered) { // recno-renumber database 425 /* 426 * Close cursor during append and then reopen to support 427 * CDB restriction that append may not be called with a 428 * cursor open; note the append will still fail if the 429 * application has another cursor open. 430 */ 431 close(); 432 status = coll.view.append(value, null, null); 433 cursor = new DataCursor(coll.view, writeAllowed); 434 reset(); 435 next(); // move past new record 436 } else { // hash/btree with duplicates 437 throw new IllegalStateException( 438 "Collection is empty, cannot add() duplicate"); 439 } 440 } else { // database is not empty 441 boolean putBefore = false; 442 if (coll.view.keysRenumbered) { // recno-renumber database 443 moveToCurrent(); 444 if (hasNext()) { 445 status = cursor.putBefore(value); 446 putBefore = true; 447 } else { 448 status = cursor.putAfter(value); 449 } 450 } else { // hash/btree with duplicates 451 if (coll.areDuplicatesOrdered()) { 452 status = cursor.putNoDupData(null, value, null, true); 453 } else if (toNext == 0) { 454 status = cursor.putBefore(value); 455 putBefore = true; 456 } else { 457 status = cursor.putAfter(value); 458 } 459 } 460 if (putBefore) { 461 toPrevious = 0; 462 toNext = MOVE_NEXT; 463 } 464 } 465 if (status == OperationStatus.KEYEXIST) { 466 throw new IllegalArgumentException("Duplicate value"); 467 } else if (status != OperationStatus.SUCCESS) { 468 throw new IllegalArgumentException("Could not insert: " + 469 status); 470 } 471 setAndRemoveAllowed = false; 472 } catch (Exception e) { 473 throw StoredContainer.convertException(e); 474 } 475 } 476 477 // --- end Iterator/ListIterator methods --- 478 479 /** 480 * Resets cursor to an uninitialized state. 481 */ 482 private void reset() { 483 484 toNext = MOVE_FIRST; 485 toPrevious = MOVE_PREV; 486 toCurrent = 0; 487 currentData = null; 488 /* 489 * Initialize cursor at beginning to avoid "initial previous == last" 490 * behavior when cursor is uninitialized. 491 * 492 * FindBugs whines about us ignoring the return value from hasNext(). 493 */ 494 hasNext(); 495 } 496 497 /** 498 * Returns the number of elements having the same key value as the key 499 * value of the element last returned by next() or previous(). If no 500 * duplicates are allowed, 1 is always returned. 501 * This method does not exist in the standard {@link Iterator} or {@link 502 * ListIterator} interfaces. 503 * 504 * @return the number of duplicates. 505 * 506 * @throws IllegalStateException if next() or previous() has not been 507 * called for this iterator, or if remove() or add() were called after 508 * the last call to next() or previous(). 509 */ 510 public int count() { 511 512 if (!setAndRemoveAllowed) throw new IllegalStateException(); 513 try { 514 moveToCurrent(); 515 return cursor.count(); 516 } catch (Exception e) { 517 throw StoredContainer.convertException(e); 518 } 519 } 520 521 /** 522 * Closes this iterator. 523 * This method does not exist in the standard {@link Iterator} or {@link 524 * ListIterator} interfaces. 525 * 526 * <p>After being closed, only the {@link #hasNext} and {@link 527 * #hasPrevious} methods may be called and these will return false. {@link 528 * #close()} may also be called again and will do nothing. If other 529 * methods are called a <code>NullPointerException</code> will generally be 530 * thrown.</p> 531 * 532 * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is 533 * thrown. 534 */ 535 public void close() { 536 537 if (cursor != null) { 538 coll.closeCursor(cursor); 539 cursor = null; 540 } 541 } 542 543 /** 544 * Returns the collection associated with this iterator. 545 * This method does not exist in the standard {@link Iterator} or {@link 546 * ListIterator} interfaces. 547 * 548 * @return the collection associated with this iterator. 549 */ 550 public final StoredCollection getCollection() { 551 552 return coll; 553 } 554 555 // --- begin BaseIterator methods --- 556 557 public final ListIterator dup() { 558 559 try { 560 StoredIterator o = (StoredIterator) super.clone(); 561 o.cursor = cursor.cloneCursor(); 562 return o; 563 } catch (Exception e) { 564 throw StoredContainer.convertException(e); 565 } 566 } 567 568 public final boolean isCurrentData(Object currentData) { 569 570 return (this.currentData == currentData); 571 } 572 573 public final boolean moveToIndex(int index) { 574 575 try { 576 OperationStatus status = 577 cursor.getSearchKey(new Integer(index), null, lockForWrite); 578 setAndRemoveAllowed = (status == OperationStatus.SUCCESS); 579 return setAndRemoveAllowed; 580 } catch (Exception e) { 581 throw StoredContainer.convertException(e); 582 } 583 } 584 585 // --- end BaseIterator methods --- 586 587 private void moveToCurrent() 588 throws DatabaseException { 589 590 if (toCurrent != 0) { 591 move(toCurrent); 592 toCurrent = 0; 593 } 594 } 595 596 private OperationStatus move(int direction) 597 throws DatabaseException { 598 599 switch (direction) { 600 case MOVE_NEXT: 601 if (coll.iterateDuplicates()) { 602 return cursor.getNext(lockForWrite); 603 } else { 604 return cursor.getNextNoDup(lockForWrite); 605 } 606 case MOVE_PREV: 607 if (coll.iterateDuplicates()) { 608 return cursor.getPrev(lockForWrite); 609 } else { 610 return cursor.getPrevNoDup(lockForWrite); 611 } 612 case MOVE_FIRST: 613 return cursor.getFirst(lockForWrite); 614 default: 615 throw new IllegalArgumentException(String.valueOf(direction)); 616 } 617 } 618} 619