2 * See the file LICENSE for redistribution information.
4 * Copyright (c) 2000, 2012 Oracle and/or its affiliates. All rights reserved.
8 package com.sleepycat.collections;
10 import java.io.Closeable;
11 import java.util.Iterator;
12 import java.util.ListIterator;
13 import java.util.NoSuchElementException;
15 import com.sleepycat.compat.DbCompat;
16 import com.sleepycat.db.DatabaseException;
17 import com.sleepycat.db.OperationStatus;
18 import com.sleepycat.util.RuntimeExceptionWrapper;
21 * The Iterator returned by all stored collections.
23 * <p>While in general this class conforms to the {@link Iterator} interface,
24 * it is important to note that all iterators for stored collections must be
25 * explicitly closed with {@link #close()}. The static method {@link
26 * #close(java.util.Iterator)} allows calling close for all iterators without
27 * harm to iterators that are not from stored collections, and also avoids
28 * casting. If a stored iterator is not closed, unpredictable behavior
29 * including process death may result.</p>
31 * <p>This class implements the {@link Iterator} interface for all stored
32 * iterators. It also implements {@link ListIterator} because some list
33 * iterator methods apply to all stored iterators, for example, {@link
34 * #previous} and {@link #hasPrevious}. Other list iterator methods are always
35 * supported for lists, but for other types of collections are only supported
36 * under certain conditions. See {@link #nextIndex}, {@link #previousIndex},
37 * {@link #add} and {@link #set} for details.</p>
39 * <p>In addition, this class provides the following methods for stored
40 * collection iterators only. Note that the use of these methods is not
41 * compatible with the standard Java collections interface.</p>
43 * <li>{@link #close()}</li>
44 * <li>{@link #close(Iterator)}</li>
45 * <li>{@link #count()}</li>
46 * <li>{@link #getCollection}</li>
47 * <li>{@link #setReadModifyWrite}</li>
48 * <li>{@link #isReadModifyWrite}</li>
53 public class StoredIterator<E> extends BaseIterator<E>
54 implements ListIterator<E>, Cloneable
58 * Closes the given iterator using {@link #close()} if it is a {@link
59 * StoredIterator}. If the given iterator is not a {@link StoredIterator},
60 * this method does nothing.
62 * @param i is the iterator to close.
64 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
65 * including a {@code DatabaseException} on BDB (C edition).
67 public static void close(Iterator<?> i) {
69 if (i instanceof StoredIterator) {
70 ((StoredIterator) i).close();
74 private static final int MOVE_NEXT = 1;
75 private static final int MOVE_PREV = 2;
76 private static final int MOVE_FIRST = 3;
78 private boolean lockForWrite;
79 private StoredCollection<E> coll;
80 private DataCursor cursor;
82 private int toPrevious;
83 private int toCurrent;
84 private boolean writeAllowed;
85 private boolean setAndRemoveAllowed;
86 private E currentData;
88 StoredIterator(StoredCollection<E> coll,
90 DataCursor joinCursor) {
93 this.writeAllowed = writeAllowed;
94 if (joinCursor == null)
95 this.cursor = new DataCursor(coll.view, writeAllowed);
97 this.cursor = joinCursor;
99 } catch (Exception e) {
101 /* Ensure that the cursor is closed. [#10516] */
103 } catch (Exception ignored) {
106 throw StoredContainer.convertException(e);
111 * Returns whether write-locks will be obtained when reading with this
113 * Obtaining write-locks can prevent deadlocks when reading and then
116 * @return the write-lock setting.
118 public final boolean isReadModifyWrite() {
124 * Changes whether write-locks will be obtained when reading with this
126 * Obtaining write-locks can prevent deadlocks when reading and then
129 * @param lockForWrite the write-lock setting.
131 public void setReadModifyWrite(boolean lockForWrite) {
133 this.lockForWrite = lockForWrite;
136 // --- begin Iterator/ListIterator methods ---
139 * Returns true if this iterator has more elements when traversing in the
140 * forward direction. False is returned if the iterator has been closed.
141 * This method conforms to the {@link Iterator#hasNext} interface.
143 * @return whether {@link #next()} will succeed.
146 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
147 * including a {@code DatabaseException} on BDB (C edition).
149 public boolean hasNext() {
151 if (cursor == null) {
156 OperationStatus status = move(toNext);
157 if (status == OperationStatus.SUCCESS) {
159 toPrevious = MOVE_PREV;
160 toCurrent = MOVE_PREV;
163 return (toNext == 0);
164 } catch (Exception e) {
165 throw StoredContainer.convertException(e);
170 * Returns true if this iterator has more elements when traversing in the
171 * reverse direction. It returns false if the iterator has been closed.
172 * This method conforms to the {@link ListIterator#hasPrevious} interface.
174 * @return whether {@link #previous()} will succeed.
177 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
178 * including a {@code DatabaseException} on BDB (C edition).
180 public boolean hasPrevious() {
182 if (cursor == null) {
186 if (toPrevious != 0) {
187 OperationStatus status = move(toPrevious);
188 if (status == OperationStatus.SUCCESS) {
191 toCurrent = MOVE_NEXT;
194 return (toPrevious == 0);
195 } catch (Exception e) {
196 throw StoredContainer.convertException(e);
201 * Returns the next element in the iteration.
202 * This method conforms to the {@link Iterator#next} interface.
204 * @return the next element.
207 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
208 * including a {@code DatabaseException} on BDB (C Edition).
214 OperationStatus status = move(toNext);
215 if (status == OperationStatus.SUCCESS) {
220 currentData = coll.makeIteratorData(this, cursor);
224 setAndRemoveAllowed = true;
227 // else throw NoSuchElementException below
228 } catch (Exception e) {
229 throw StoredContainer.convertException(e);
231 throw new NoSuchElementException();
235 * Returns the next element in the iteration.
236 * This method conforms to the {@link ListIterator#previous} interface.
238 * @return the previous element.
241 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
242 * including a {@code DatabaseException} on BDB (C Edition).
244 public E previous() {
247 if (toPrevious != 0) {
248 OperationStatus status = move(toPrevious);
249 if (status == OperationStatus.SUCCESS) {
253 if (toPrevious == 0) {
254 currentData = coll.makeIteratorData(this, cursor);
255 toPrevious = MOVE_PREV;
258 setAndRemoveAllowed = true;
261 // else throw NoSuchElementException below
262 } catch (Exception e) {
263 throw StoredContainer.convertException(e);
265 throw new NoSuchElementException();
269 * Returns the index of the element that would be returned by a subsequent
271 * This method conforms to the {@link ListIterator#nextIndex} interface
272 * except that it returns Integer.MAX_VALUE for stored lists when
273 * positioned at the end of the list, rather than returning the list size
274 * as specified by the ListIterator interface. This is because the database
275 * size is not available.
277 * @return the next index.
280 * @throws UnsupportedOperationException if this iterator's collection does
281 * not use record number keys.
283 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
284 * including a {@code DatabaseException} on BDB (C Edition).
286 public int nextIndex() {
288 if (!coll.view.recNumAccess) {
289 throw new UnsupportedOperationException
290 ("Record number access not supported");
293 return hasNext() ? (cursor.getCurrentRecordNumber() -
294 coll.getIndexOffset())
296 } catch (Exception e) {
297 throw StoredContainer.convertException(e);
302 * Returns the index of the element that would be returned by a subsequent
304 * This method conforms to the {@link ListIterator#previousIndex}
307 * @return the previous index.
310 * @throws UnsupportedOperationException if this iterator's collection does
311 * not use record number keys.
313 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
314 * including a {@code DatabaseException} on BDB (C Edition).
316 public int previousIndex() {
318 if (!coll.view.recNumAccess) {
319 throw new UnsupportedOperationException
320 ("Record number access not supported");
323 return hasPrevious() ? (cursor.getCurrentRecordNumber() -
324 coll.getIndexOffset())
326 } catch (Exception e) {
327 throw StoredContainer.convertException(e);
332 * Replaces the last element returned by next or previous with the
333 * specified element (optional operation).
334 * This method conforms to the {@link ListIterator#set} interface.
336 * <p>In order to call this method, if the underlying Database is
337 * transactional then a transaction must be active when creating the
340 * @param value the new value.
343 * @throws UnsupportedOperationException if the collection is a {@link
344 * StoredKeySet} (the set returned by {@link java.util.Map#keySet}), or if
345 * duplicates are sorted since this would change the iterator position, or
346 * if the collection is indexed, or if the collection is read-only.
348 * @throws IllegalArgumentException if an entity value binding is used and
349 * the primary key of the value given is different than the existing stored
352 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
353 * including a {@code DatabaseException} on BDB (C Edition).
355 public void set(E value) {
357 if (!coll.hasValues()) {
358 throw new UnsupportedOperationException();
360 if (!setAndRemoveAllowed) {
361 throw new IllegalStateException();
365 cursor.putCurrent(value);
366 } catch (Exception e) {
367 throw StoredContainer.convertException(e);
372 * Removes the last element that was returned by next or previous (optional
374 * This method conforms to the {@link ListIterator#remove} interface except
375 * that when the collection is a list and the RECNO-RENUMBER access method
376 * is not used, list indices will not be renumbered.
378 * <p>In order to call this method, if the underlying Database is
379 * transactional then a transaction must be active when creating the
382 * <p>Note that for the JE product, RECNO-RENUMBER databases are not
383 * supported, and therefore list indices are never renumbered by this
387 * @throws UnsupportedOperationException if the collection is a sublist, or
388 * if the collection is read-only.
390 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
391 * including a {@code DatabaseException} on BDB (C Edition).
393 public void remove() {
395 if (!setAndRemoveAllowed) {
396 throw new IllegalStateException();
401 setAndRemoveAllowed = false;
403 toPrevious = MOVE_PREV;
404 } catch (Exception e) {
405 throw StoredContainer.convertException(e);
410 * Inserts the specified element into the list or inserts a duplicate into
411 * other types of collections (optional operation).
412 * This method conforms to the {@link ListIterator#add} interface when
413 * the collection is a list and the RECNO-RENUMBER access method is used.
414 * Otherwise, this method may only be called when duplicates are allowed.
415 * If duplicates are unsorted, the new value will be inserted in the same
416 * manner as list elements.
417 * If duplicates are sorted, the new value will be inserted in sort order.
419 * <p>Note that for the JE product, RECNO-RENUMBER databases are not
420 * supported, and therefore this method may only be used to add
423 * @param value the new value.
426 * @throws UnsupportedOperationException if the collection is a sublist, or
427 * if the collection is indexed, or if the collection is read-only, or if
428 * the collection is a list and the RECNO-RENUMBER access method was not
429 * used, or if the collection is not a list and duplicates are not allowed.
431 * @throws IllegalStateException if the collection is empty and is not a
432 * list with RECNO-RENUMBER access.
434 * @throws IllegalArgumentException if a duplicate value is being added
435 * that already exists and duplicates are sorted.
437 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
438 * including a {@code DatabaseException} on BDB (C Edition).
440 public void add(E value) {
442 coll.checkIterAddAllowed();
444 OperationStatus status = OperationStatus.SUCCESS;
445 if (toNext != 0 && toPrevious != 0) { // database is empty
446 if (coll.view.keysRenumbered) { // recno-renumber database
448 * Close cursor during append and then reopen to support
449 * CDB restriction that append may not be called with a
450 * cursor open; note the append will still fail if the
451 * application has another cursor open.
454 status = coll.view.append(value, null, null);
455 cursor = new DataCursor(coll.view, writeAllowed);
457 next(); // move past new record
458 } else { // hash/btree with duplicates
459 throw new IllegalStateException
460 ("Collection is empty, cannot add() duplicate");
462 } else { // database is not empty
463 boolean putBefore = false;
464 if (coll.view.keysRenumbered) { // recno-renumber database
467 status = cursor.putBefore(value);
470 status = cursor.putAfter(value);
472 } else { // hash/btree with duplicates
473 if (coll.areDuplicatesOrdered()) {
474 status = cursor.putNoDupData(null, value, null, true);
475 } else if (toNext == 0) {
476 status = cursor.putBefore(value);
479 status = cursor.putAfter(value);
487 if (status == OperationStatus.KEYEXIST) {
488 throw new IllegalArgumentException("Duplicate value");
489 } else if (status != OperationStatus.SUCCESS) {
490 throw DbCompat.unexpectedState("Could not insert: " + status);
492 setAndRemoveAllowed = false;
493 } catch (Exception e) {
494 throw StoredContainer.convertException(e);
498 // --- end Iterator/ListIterator methods ---
501 * Resets cursor to an uninitialized state.
503 private void reset() {
506 toPrevious = MOVE_PREV;
510 * Initialize cursor at beginning to avoid "initial previous == last"
511 * behavior when cursor is uninitialized.
513 * FindBugs whines about us ignoring the return value from hasNext().
519 * Returns the number of elements having the same key value as the key
520 * value of the element last returned by next() or previous(). If no
521 * duplicates are allowed, 1 is always returned.
522 * This method does not exist in the standard {@link Iterator} or {@link
523 * ListIterator} interfaces.
526 * @return the number of duplicates.
528 * @throws IllegalStateException if next() or previous() has not been
529 * called for this iterator, or if remove() or add() were called after
530 * the last call to next() or previous().
534 if (!setAndRemoveAllowed) {
535 throw new IllegalStateException();
539 return cursor.count();
540 } catch (Exception e) {
541 throw StoredContainer.convertException(e);
546 * Closes this iterator.
547 * This method does not exist in the standard {@link Iterator} or {@link
548 * ListIterator} interfaces.
550 * <p>After being closed, only the {@link #hasNext} and {@link
551 * #hasPrevious} methods may be called and these will return false. {@link
552 * #close()} may also be called again and will do nothing. If other
553 * methods are called a <code>NullPointerException</code> will generally be
556 * @throws RuntimeExceptionWrapper if a checked exception is thrown,
557 * including a {@code DatabaseException} on BDB (C Edition).
559 public void close() {
561 if (cursor != null) {
562 coll.closeCursor(cursor);
568 * Returns the collection associated with this iterator.
569 * This method does not exist in the standard {@link Iterator} or {@link
570 * ListIterator} interfaces.
572 * @return the collection associated with this iterator.
574 public final StoredCollection<E> getCollection() {
579 // --- begin BaseIterator methods ---
581 final ListIterator<E> dup() {
584 StoredIterator o = (StoredIterator) super.clone();
585 o.cursor = cursor.cloneCursor();
587 } catch (Exception e) {
588 throw StoredContainer.convertException(e);
592 final boolean isCurrentData(Object currentData) {
594 return (this.currentData == currentData);
597 final boolean moveToIndex(int index) {
600 OperationStatus status =
601 cursor.getSearchKey(Integer.valueOf(index),
603 setAndRemoveAllowed = (status == OperationStatus.SUCCESS);
604 return setAndRemoveAllowed;
605 } catch (Exception e) {
606 throw StoredContainer.convertException(e);
610 // --- end BaseIterator methods ---
612 private void moveToCurrent()
613 throws DatabaseException {
615 if (toCurrent != 0) {
621 private OperationStatus move(int direction)
622 throws DatabaseException {
626 if (coll.iterateDuplicates()) {
627 return cursor.getNext(lockForWrite);
629 return cursor.getNextNoDup(lockForWrite);
632 if (coll.iterateDuplicates()) {
633 return cursor.getPrev(lockForWrite);
635 return cursor.getPrevNoDup(lockForWrite);
638 return cursor.getFirst(lockForWrite);
640 throw DbCompat.unexpectedState(String.valueOf(direction));