| /*- |
| * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved. |
| * |
| * This file was distributed by Oracle as part of a version of Oracle Berkeley |
| * DB Java Edition made available at: |
| * |
| * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html |
| * |
| * Please see the LICENSE file included in the top-level directory of the |
| * appropriate version of Oracle Berkeley DB Java Edition for a copy of the |
| * license and additional information. |
| */ |
| |
| package com.sleepycat.collections; |
| |
| import java.io.Closeable; |
| import java.util.Iterator; |
| import java.util.ListIterator; |
| import java.util.NoSuchElementException; |
| |
| import com.sleepycat.compat.DbCompat; |
| import com.sleepycat.je.DatabaseException; |
| /* <!-- begin JE only --> */ |
| import com.sleepycat.je.EnvironmentFailureException; // for javadoc |
| import com.sleepycat.je.OperationFailureException; // for javadoc |
| /* <!-- end JE only --> */ |
| import com.sleepycat.je.OperationStatus; |
| import com.sleepycat.util.RuntimeExceptionWrapper; |
| |
| /** |
| * The Iterator returned by all stored collections. |
| * |
| * <p>While in general this class conforms to the {@link Iterator} interface, |
| * it is important to note that all iterators for stored collections must be |
| * explicitly closed with {@link #close()}. The static method {@link |
| * #close(java.util.Iterator)} allows calling close for all iterators without |
| * harm to iterators that are not from stored collections, and also avoids |
| * casting. If a stored iterator is not closed, unpredictable behavior |
| * including process death may result.</p> |
| * |
| * <p>This class implements the {@link Iterator} interface for all stored |
| * iterators. It also implements {@link ListIterator} because some list |
| * iterator methods apply to all stored iterators, for example, {@link |
| * #previous} and {@link #hasPrevious}. Other list iterator methods are always |
| * supported for lists, but for other types of collections are only supported |
| * under certain conditions. See {@link #nextIndex}, {@link #previousIndex}, |
| * {@link #add} and {@link #set} for details.</p> |
| * |
| * <p>In addition, this class provides the following methods for stored |
| * collection iterators only. Note that the use of these methods is not |
| * compatible with the standard Java collections interface.</p> |
| * <ul> |
| * <li>{@link #close()}</li> |
| * <li>{@link #close(Iterator)}</li> |
| * <li>{@link #count()}</li> |
| * <li>{@link #getCollection}</li> |
| * <li>{@link #setReadModifyWrite}</li> |
| * <li>{@link #isReadModifyWrite}</li> |
| * </ul> |
| * |
| * @author Mark Hayes |
| */ |
| public class StoredIterator<E> extends BaseIterator<E> |
| implements ListIterator<E>, Cloneable |
| /* <!-- begin JE only --> */ |
| , Closeable |
| /* <!-- end JE only --> */ |
| { |
| |
| /** |
| * Closes the given iterator using {@link #close()} if it is a {@link |
| * StoredIterator}. If the given iterator is not a {@link StoredIterator}, |
| * this method does nothing. |
| * |
| * @param i is the iterator to close. |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C edition). |
| */ |
| public static void close(Iterator<?> i) { |
| |
| if (i instanceof StoredIterator) { |
| ((StoredIterator) i).close(); |
| } |
| } |
| |
| private static final int MOVE_NEXT = 1; |
| private static final int MOVE_PREV = 2; |
| private static final int MOVE_FIRST = 3; |
| |
| private boolean lockForWrite; |
| private StoredCollection<E> coll; |
| private DataCursor cursor; |
| private int toNext; |
| private int toPrevious; |
| private int toCurrent; |
| private boolean writeAllowed; |
| private boolean setAndRemoveAllowed; |
| private E currentData; |
| |
| StoredIterator(StoredCollection<E> coll, |
| boolean writeAllowed, |
| DataCursor joinCursor) { |
| try { |
| this.coll = coll; |
| this.writeAllowed = writeAllowed; |
| if (joinCursor == null) |
| this.cursor = new DataCursor(coll.view, writeAllowed); |
| else |
| this.cursor = joinCursor; |
| reset(); |
| } catch (Exception e) { |
| try { |
| /* Ensure that the cursor is closed. [#10516] */ |
| close(); |
| } catch (Exception ignored) { |
| /* Klockwork - ok */ |
| } |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| /** |
| * Returns whether write-locks will be obtained when reading with this |
| * cursor. |
| * Obtaining write-locks can prevent deadlocks when reading and then |
| * modifying data. |
| * |
| * @return the write-lock setting. |
| */ |
| public final boolean isReadModifyWrite() { |
| |
| return lockForWrite; |
| } |
| |
| /** |
| * Changes whether write-locks will be obtained when reading with this |
| * cursor. |
| * Obtaining write-locks can prevent deadlocks when reading and then |
| * modifying data. |
| * |
| * @param lockForWrite the write-lock setting. |
| */ |
| public void setReadModifyWrite(boolean lockForWrite) { |
| |
| this.lockForWrite = lockForWrite; |
| } |
| |
| // --- begin Iterator/ListIterator methods --- |
| |
| /** |
| * Returns true if this iterator has more elements when traversing in the |
| * forward direction. False is returned if the iterator has been closed. |
| * This method conforms to the {@link Iterator#hasNext} interface. |
| * |
| * @return whether {@link #next()} will succeed. |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#readFailures">Read Operation |
| * Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C edition). |
| */ |
| public boolean hasNext() { |
| |
| if (cursor == null) { |
| return false; |
| } |
| try { |
| if (toNext != 0) { |
| OperationStatus status = move(toNext); |
| if (status == OperationStatus.SUCCESS) { |
| toNext = 0; |
| toPrevious = MOVE_PREV; |
| toCurrent = MOVE_PREV; |
| } |
| } |
| return (toNext == 0); |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| /** |
| * Returns true if this iterator has more elements when traversing in the |
| * reverse direction. It returns false if the iterator has been closed. |
| * This method conforms to the {@link ListIterator#hasPrevious} interface. |
| * |
| * @return whether {@link #previous()} will succeed. |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#readFailures">Read Operation |
| * Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C edition). |
| */ |
| public boolean hasPrevious() { |
| |
| if (cursor == null) { |
| return false; |
| } |
| try { |
| if (toPrevious != 0) { |
| OperationStatus status = move(toPrevious); |
| if (status == OperationStatus.SUCCESS) { |
| toPrevious = 0; |
| toNext = MOVE_NEXT; |
| toCurrent = MOVE_NEXT; |
| } |
| } |
| return (toPrevious == 0); |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| /** |
| * Returns the next element in the iteration. |
| * This method conforms to the {@link Iterator#next} interface. |
| * |
| * @return the next element. |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#readFailures">Read Operation |
| * Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C Edition). |
| */ |
| public E next() { |
| |
| try { |
| if (toNext != 0) { |
| OperationStatus status = move(toNext); |
| if (status == OperationStatus.SUCCESS) { |
| toNext = 0; |
| } |
| } |
| if (toNext == 0) { |
| currentData = coll.makeIteratorData(this, cursor); |
| toNext = MOVE_NEXT; |
| toPrevious = 0; |
| toCurrent = 0; |
| setAndRemoveAllowed = true; |
| return currentData; |
| } |
| // else throw NoSuchElementException below |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| throw new NoSuchElementException(); |
| } |
| |
| /** |
| * Returns the next element in the iteration. |
| * This method conforms to the {@link ListIterator#previous} interface. |
| * |
| * @return the previous element. |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#readFailures">Read Operation |
| * Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C Edition). |
| */ |
| public E previous() { |
| |
| try { |
| if (toPrevious != 0) { |
| OperationStatus status = move(toPrevious); |
| if (status == OperationStatus.SUCCESS) { |
| toPrevious = 0; |
| } |
| } |
| if (toPrevious == 0) { |
| currentData = coll.makeIteratorData(this, cursor); |
| toPrevious = MOVE_PREV; |
| toNext = 0; |
| toCurrent = 0; |
| setAndRemoveAllowed = true; |
| return currentData; |
| } |
| // else throw NoSuchElementException below |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| throw new NoSuchElementException(); |
| } |
| |
| /** |
| * Returns the index of the element that would be returned by a subsequent |
| * call to next. |
| * This method conforms to the {@link ListIterator#nextIndex} interface |
| * except that it returns Integer.MAX_VALUE for stored lists when |
| * positioned at the end of the list, rather than returning the list size |
| * as specified by the ListIterator interface. This is because the database |
| * size is not available. |
| * |
| * @return the next index. |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#readFailures">Read Operation |
| * Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @throws UnsupportedOperationException if this iterator's collection does |
| * not use record number keys. |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C Edition). |
| */ |
| public int nextIndex() { |
| |
| if (!coll.view.recNumAccess) { |
| throw new UnsupportedOperationException |
| ("Record number access not supported"); |
| } |
| try { |
| return hasNext() ? (cursor.getCurrentRecordNumber() - |
| coll.getIndexOffset()) |
| : Integer.MAX_VALUE; |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| /** |
| * Returns the index of the element that would be returned by a subsequent |
| * call to previous. |
| * This method conforms to the {@link ListIterator#previousIndex} |
| * interface. |
| * |
| * @return the previous index. |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#readFailures">Read Operation |
| * Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @throws UnsupportedOperationException if this iterator's collection does |
| * not use record number keys. |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C Edition). |
| */ |
| public int previousIndex() { |
| |
| if (!coll.view.recNumAccess) { |
| throw new UnsupportedOperationException |
| ("Record number access not supported"); |
| } |
| try { |
| return hasPrevious() ? (cursor.getCurrentRecordNumber() - |
| coll.getIndexOffset()) |
| : (-1); |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| /** |
| * Replaces the last element returned by next or previous with the |
| * specified element (optional operation). |
| * This method conforms to the {@link ListIterator#set} interface. |
| * |
| * <p>In order to call this method, if the underlying Database is |
| * transactional then a transaction must be active when creating the |
| * iterator.</p> |
| * |
| * @param value the new value. |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#writeFailures">Write |
| * Operation Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @throws UnsupportedOperationException if the collection is a {@link |
| * StoredKeySet} (the set returned by {@link java.util.Map#keySet}), or if |
| * duplicates are sorted since this would change the iterator position, or |
| * if the collection is indexed, or if the collection is read-only. |
| * |
| * @throws IllegalArgumentException if an entity value binding is used and |
| * the primary key of the value given is different than the existing stored |
| * primary key. |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C Edition). |
| */ |
| public void set(E value) { |
| |
| if (!coll.hasValues()) { |
| throw new UnsupportedOperationException(); |
| } |
| if (!setAndRemoveAllowed) { |
| throw new IllegalStateException(); |
| } |
| try { |
| moveToCurrent(); |
| cursor.putCurrent(value); |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| /** |
| * Removes the last element that was returned by next or previous (optional |
| * operation). |
| * This method conforms to the {@link ListIterator#remove} interface except |
| * that when the collection is a list and the RECNO-RENUMBER access method |
| * is not used, list indices will not be renumbered. |
| * |
| * <p>In order to call this method, if the underlying Database is |
| * transactional then a transaction must be active when creating the |
| * iterator.</p> |
| * |
| * <p>Note that for the JE product, RECNO-RENUMBER databases are not |
| * supported, and therefore list indices are never renumbered by this |
| * method.</p> |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#writeFailures">Write |
| * Operation Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @throws UnsupportedOperationException if the collection is a sublist, or |
| * if the collection is read-only. |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C Edition). |
| */ |
| public void remove() { |
| |
| if (!setAndRemoveAllowed) { |
| throw new IllegalStateException(); |
| } |
| try { |
| moveToCurrent(); |
| cursor.delete(); |
| setAndRemoveAllowed = false; |
| toNext = MOVE_NEXT; |
| toPrevious = MOVE_PREV; |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| /** |
| * Inserts the specified element into the list or inserts a duplicate into |
| * other types of collections (optional operation). |
| * This method conforms to the {@link ListIterator#add} interface when |
| * the collection is a list and the RECNO-RENUMBER access method is used. |
| * Otherwise, this method may only be called when duplicates are allowed. |
| * If duplicates are unsorted, the new value will be inserted in the same |
| * manner as list elements. |
| * If duplicates are sorted, the new value will be inserted in sort order. |
| * |
| * <p>Note that for the JE product, RECNO-RENUMBER databases are not |
| * supported, and therefore this method may only be used to add |
| * duplicates.</p> |
| * |
| * @param value the new value. |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#writeFailures">Write |
| * Operation Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @throws UnsupportedOperationException if the collection is a sublist, or |
| * if the collection is indexed, or if the collection is read-only, or if |
| * the collection is a list and the RECNO-RENUMBER access method was not |
| * used, or if the collection is not a list and duplicates are not allowed. |
| * |
| * @throws IllegalStateException if the collection is empty and is not a |
| * list with RECNO-RENUMBER access. |
| * |
| * @throws IllegalArgumentException if a duplicate value is being added |
| * that already exists and duplicates are sorted. |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C Edition). |
| */ |
| public void add(E value) { |
| |
| coll.checkIterAddAllowed(); |
| try { |
| OperationStatus status = OperationStatus.SUCCESS; |
| if (toNext != 0 && toPrevious != 0) { // database is empty |
| if (coll.view.keysRenumbered) { // recno-renumber database |
| /* |
| * Close cursor during append and then reopen to support |
| * CDB restriction that append may not be called with a |
| * cursor open; note the append will still fail if the |
| * application has another cursor open. |
| */ |
| close(); |
| status = coll.view.append(value, null, null); |
| cursor = new DataCursor(coll.view, writeAllowed); |
| reset(); |
| next(); // move past new record |
| } else { // hash/btree with duplicates |
| throw new IllegalStateException |
| ("Collection is empty, cannot add() duplicate"); |
| } |
| } else { // database is not empty |
| boolean putBefore = false; |
| if (coll.view.keysRenumbered) { // recno-renumber database |
| moveToCurrent(); |
| if (hasNext()) { |
| status = cursor.putBefore(value); |
| putBefore = true; |
| } else { |
| status = cursor.putAfter(value); |
| } |
| } else { // hash/btree with duplicates |
| if (coll.areDuplicatesOrdered()) { |
| status = cursor.putNoDupData(null, value, null, true); |
| } else if (toNext == 0) { |
| status = cursor.putBefore(value); |
| putBefore = true; |
| } else { |
| status = cursor.putAfter(value); |
| } |
| } |
| if (putBefore) { |
| toPrevious = 0; |
| toNext = MOVE_NEXT; |
| } |
| } |
| if (status == OperationStatus.KEYEXIST) { |
| throw new IllegalArgumentException("Duplicate value"); |
| } else if (status != OperationStatus.SUCCESS) { |
| throw DbCompat.unexpectedState("Could not insert: " + status); |
| } |
| setAndRemoveAllowed = false; |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| // --- end Iterator/ListIterator methods --- |
| |
| /** |
| * Resets cursor to an uninitialized state. |
| */ |
| private void reset() { |
| |
| toNext = MOVE_FIRST; |
| toPrevious = MOVE_PREV; |
| toCurrent = 0; |
| currentData = null; |
| /* |
| * Initialize cursor at beginning to avoid "initial previous == last" |
| * behavior when cursor is uninitialized. |
| * |
| * FindBugs whines about us ignoring the return value from hasNext(). |
| */ |
| hasNext(); |
| } |
| |
| /** |
| * Returns the number of elements having the same key value as the key |
| * value of the element last returned by next() or previous(). If no |
| * duplicates are allowed, 1 is always returned. |
| * This method does not exist in the standard {@link Iterator} or {@link |
| * ListIterator} interfaces. |
| * |
| * <!-- begin JE only --> |
| * @throws OperationFailureException if one of the <a |
| * href="../je/OperationFailureException.html#readFailures">Read Operation |
| * Failures</a> occurs. |
| * |
| * @throws EnvironmentFailureException if an unexpected, internal or |
| * environment-wide failure occurs. |
| * <!-- end JE only --> |
| * |
| * @return the number of duplicates. |
| * |
| * @throws IllegalStateException if next() or previous() has not been |
| * called for this iterator, or if remove() or add() were called after |
| * the last call to next() or previous(). |
| */ |
| public int count() { |
| |
| if (!setAndRemoveAllowed) { |
| throw new IllegalStateException(); |
| } |
| try { |
| moveToCurrent(); |
| return cursor.count(); |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| /** |
| * Closes this iterator. |
| * This method does not exist in the standard {@link Iterator} or {@link |
| * ListIterator} interfaces. |
| * |
| * <p>After being closed, only the {@link #hasNext} and {@link |
| * #hasPrevious} methods may be called and these will return false. {@link |
| * #close()} may also be called again and will do nothing. If other |
| * methods are called a <code>NullPointerException</code> will generally be |
| * thrown.</p> |
| * |
| * @throws RuntimeExceptionWrapper if a checked exception is thrown, |
| * including a {@code DatabaseException} on BDB (C Edition). |
| */ |
| public void close() { |
| |
| if (cursor != null) { |
| coll.closeCursor(cursor); |
| cursor = null; |
| } |
| } |
| |
| /** |
| * Returns the collection associated with this iterator. |
| * This method does not exist in the standard {@link Iterator} or {@link |
| * ListIterator} interfaces. |
| * |
| * @return the collection associated with this iterator. |
| */ |
| public final StoredCollection<E> getCollection() { |
| |
| return coll; |
| } |
| |
| // --- begin BaseIterator methods --- |
| |
| final ListIterator<E> dup() { |
| |
| try { |
| StoredIterator o = (StoredIterator) super.clone(); |
| o.cursor = cursor.cloneCursor(); |
| return o; |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| final boolean isCurrentData(Object currentData) { |
| |
| return (this.currentData == currentData); |
| } |
| |
| final boolean moveToIndex(int index) { |
| |
| try { |
| OperationStatus status = |
| cursor.getSearchKey(Integer.valueOf(index), |
| null, lockForWrite); |
| setAndRemoveAllowed = (status == OperationStatus.SUCCESS); |
| return setAndRemoveAllowed; |
| } catch (Exception e) { |
| throw StoredContainer.convertException(e); |
| } |
| } |
| |
| // --- end BaseIterator methods --- |
| |
| private void moveToCurrent() |
| throws DatabaseException { |
| |
| if (toCurrent != 0) { |
| move(toCurrent); |
| toCurrent = 0; |
| } |
| } |
| |
| private OperationStatus move(int direction) |
| throws DatabaseException { |
| |
| switch (direction) { |
| case MOVE_NEXT: |
| if (coll.iterateDuplicates()) { |
| return cursor.getNext(lockForWrite); |
| } else { |
| return cursor.getNextNoDup(lockForWrite); |
| } |
| case MOVE_PREV: |
| if (coll.iterateDuplicates()) { |
| return cursor.getPrev(lockForWrite); |
| } else { |
| return cursor.getPrevNoDup(lockForWrite); |
| } |
| case MOVE_FIRST: |
| return cursor.getFirst(lockForWrite); |
| default: |
| throw DbCompat.unexpectedState(String.valueOf(direction)); |
| } |
| } |
| } |