Imported Upstream version 5.3.21
[platform/upstream/libdb.git] / lang / java / src / com / sleepycat / collections / StoredIterator.java
1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2000, 2012 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7
8 package com.sleepycat.collections;
9
10 import java.io.Closeable;
11 import java.util.Iterator;
12 import java.util.ListIterator;
13 import java.util.NoSuchElementException;
14
15 import com.sleepycat.compat.DbCompat;
16 import com.sleepycat.db.DatabaseException;
17 import com.sleepycat.db.OperationStatus;
18 import com.sleepycat.util.RuntimeExceptionWrapper;
19
20 /**
21  * The Iterator returned by all stored collections.
22  *
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>
30  *
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>
38  *
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>
42  * <ul>
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>
49  * </ul>
50  *
51  * @author Mark Hayes
52  */
53 public class StoredIterator<E> extends BaseIterator<E>
54     implements ListIterator<E>, Cloneable
55     {
56
57     /**
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.
61      *
62      * @param i is the iterator to close.
63      *
64      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
65      * including a {@code DatabaseException} on BDB (C edition).
66      */
67     public static void close(Iterator<?> i) {
68
69         if (i instanceof StoredIterator) {
70             ((StoredIterator) i).close();
71         }
72     }
73
74     private static final int MOVE_NEXT = 1;
75     private static final int MOVE_PREV = 2;
76     private static final int MOVE_FIRST = 3;
77
78     private boolean lockForWrite;
79     private StoredCollection<E> coll;
80     private DataCursor cursor;
81     private int toNext;
82     private int toPrevious;
83     private int toCurrent;
84     private boolean writeAllowed;
85     private boolean setAndRemoveAllowed;
86     private E currentData;
87
88     StoredIterator(StoredCollection<E> coll,
89                    boolean writeAllowed,
90                    DataCursor joinCursor) {
91         try {
92             this.coll = coll;
93             this.writeAllowed = writeAllowed;
94             if (joinCursor == null)
95                 this.cursor = new DataCursor(coll.view, writeAllowed);
96             else
97                 this.cursor = joinCursor;
98             reset();
99         } catch (Exception e) {
100             try {
101                 /* Ensure that the cursor is closed.  [#10516] */
102                 close();
103             } catch (Exception ignored) {
104                 /* Klockwork - ok */
105             }
106             throw StoredContainer.convertException(e);
107         }
108     }
109
110     /**
111      * Returns whether write-locks will be obtained when reading with this
112      * cursor.
113      * Obtaining write-locks can prevent deadlocks when reading and then
114      * modifying data.
115      *
116      * @return the write-lock setting.
117      */
118     public final boolean isReadModifyWrite() {
119
120         return lockForWrite;
121     }
122
123     /**
124      * Changes whether write-locks will be obtained when reading with this
125      * cursor.
126      * Obtaining write-locks can prevent deadlocks when reading and then
127      * modifying data.
128      *
129      * @param lockForWrite the write-lock setting.
130      */
131     public void setReadModifyWrite(boolean lockForWrite) {
132
133         this.lockForWrite = lockForWrite;
134     }
135
136     // --- begin Iterator/ListIterator methods ---
137
138     /**
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.
142      *
143      * @return whether {@link #next()} will succeed.
144      *
145      *
146      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
147      * including a {@code DatabaseException} on BDB (C edition).
148      */
149     public boolean hasNext() {
150
151         if (cursor == null) {
152             return false;
153         }
154         try {
155             if (toNext != 0) {
156                 OperationStatus status = move(toNext);
157                 if (status == OperationStatus.SUCCESS) {
158                     toNext = 0;
159                     toPrevious = MOVE_PREV;
160                     toCurrent = MOVE_PREV;
161                 }
162             }
163             return (toNext == 0);
164         } catch (Exception e) {
165             throw StoredContainer.convertException(e);
166         }
167     }
168
169     /**
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.
173      *
174      * @return whether {@link #previous()} will succeed.
175      *
176      *
177      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
178      * including a {@code DatabaseException} on BDB (C edition).
179      */
180     public boolean hasPrevious() {
181
182         if (cursor == null) {
183             return false;
184         }
185         try {
186             if (toPrevious != 0) {
187                 OperationStatus status = move(toPrevious);
188                 if (status == OperationStatus.SUCCESS) {
189                     toPrevious = 0;
190                     toNext = MOVE_NEXT;
191                     toCurrent = MOVE_NEXT;
192                 }
193             }
194             return (toPrevious == 0);
195         } catch (Exception e) {
196             throw StoredContainer.convertException(e);
197         }
198     }
199
200     /**
201      * Returns the next element in the iteration.
202      * This method conforms to the {@link Iterator#next} interface.
203      *
204      * @return the next element.
205      *
206      *
207      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
208      * including a {@code DatabaseException} on BDB (C Edition).
209      */
210     public E next() {
211
212         try {
213             if (toNext != 0) {
214                 OperationStatus status = move(toNext);
215                 if (status == OperationStatus.SUCCESS) {
216                     toNext = 0;
217                 }
218             }
219             if (toNext == 0) {
220                 currentData = coll.makeIteratorData(this, cursor);
221                 toNext = MOVE_NEXT;
222                 toPrevious = 0;
223                 toCurrent = 0;
224                 setAndRemoveAllowed = true;
225                 return currentData;
226             }
227             // else throw NoSuchElementException below
228         } catch (Exception e) {
229             throw StoredContainer.convertException(e);
230         }
231         throw new NoSuchElementException();
232     }
233
234     /**
235      * Returns the next element in the iteration.
236      * This method conforms to the {@link ListIterator#previous} interface.
237      *
238      * @return the previous element.
239      *
240      *
241      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
242      * including a {@code DatabaseException} on BDB (C Edition).
243      */
244     public E previous() {
245
246         try {
247             if (toPrevious != 0) {
248                 OperationStatus status = move(toPrevious);
249                 if (status == OperationStatus.SUCCESS) {
250                     toPrevious = 0;
251                 }
252             }
253             if (toPrevious == 0) {
254                 currentData = coll.makeIteratorData(this, cursor);
255                 toPrevious = MOVE_PREV;
256                 toNext = 0;
257                 toCurrent = 0;
258                 setAndRemoveAllowed = true;
259                 return currentData;
260             }
261             // else throw NoSuchElementException below
262         } catch (Exception e) {
263             throw StoredContainer.convertException(e);
264         }
265         throw new NoSuchElementException();
266     }
267
268     /**
269      * Returns the index of the element that would be returned by a subsequent
270      * call to next.
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.
276      *
277      * @return the next index.
278      *
279      *
280      * @throws UnsupportedOperationException if this iterator's collection does
281      * not use record number keys.
282      *
283      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
284      * including a {@code DatabaseException} on BDB (C Edition).
285      */
286     public int nextIndex() {
287
288         if (!coll.view.recNumAccess) {
289             throw new UnsupportedOperationException
290                 ("Record number access not supported");
291         }
292         try {
293             return hasNext() ? (cursor.getCurrentRecordNumber() -
294                                 coll.getIndexOffset())
295                              : Integer.MAX_VALUE;
296         } catch (Exception e) {
297             throw StoredContainer.convertException(e);
298         }
299     }
300
301     /**
302      * Returns the index of the element that would be returned by a subsequent
303      * call to previous.
304      * This method conforms to the {@link ListIterator#previousIndex}
305      * interface.
306      *
307      * @return the previous index.
308      *
309      *
310      * @throws UnsupportedOperationException if this iterator's collection does
311      * not use record number keys.
312      *
313      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
314      * including a {@code DatabaseException} on BDB (C Edition).
315      */
316     public int previousIndex() {
317
318         if (!coll.view.recNumAccess) {
319             throw new UnsupportedOperationException
320                 ("Record number access not supported");
321         }
322         try {
323             return hasPrevious() ? (cursor.getCurrentRecordNumber() -
324                                     coll.getIndexOffset())
325                                  : (-1);
326         } catch (Exception e) {
327             throw StoredContainer.convertException(e);
328         }
329     }
330
331     /**
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.
335      *
336      * <p>In order to call this method, if the underlying Database is
337      * transactional then a transaction must be active when creating the
338      * iterator.</p>
339      *
340      * @param value the new value.
341      *
342      *
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.
347      *
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
350      * primary key.
351      *
352      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
353      * including a {@code DatabaseException} on BDB (C Edition).
354      */
355     public void set(E value) {
356
357         if (!coll.hasValues()) {
358             throw new UnsupportedOperationException();
359         }
360         if (!setAndRemoveAllowed) {
361             throw new IllegalStateException();
362         }
363         try {
364             moveToCurrent();
365             cursor.putCurrent(value);
366         } catch (Exception e) {
367             throw StoredContainer.convertException(e);
368         }
369     }
370
371     /**
372      * Removes the last element that was returned by next or previous (optional
373      * operation).
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.
377      *
378      * <p>In order to call this method, if the underlying Database is
379      * transactional then a transaction must be active when creating the
380      * iterator.</p>
381      *
382      * <p>Note that for the JE product, RECNO-RENUMBER databases are not
383      * supported, and therefore list indices are never renumbered by this
384      * method.</p>
385      *
386      *
387      * @throws UnsupportedOperationException if the collection is a sublist, or
388      * if the collection is read-only.
389      *
390      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
391      * including a {@code DatabaseException} on BDB (C Edition).
392      */
393     public void remove() {
394
395         if (!setAndRemoveAllowed) {
396             throw new IllegalStateException();
397         }
398         try {
399             moveToCurrent();
400             cursor.delete();
401             setAndRemoveAllowed = false;
402             toNext = MOVE_NEXT;
403             toPrevious = MOVE_PREV;
404         } catch (Exception e) {
405             throw StoredContainer.convertException(e);
406         }
407     }
408
409     /**
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.
418      *
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
421      * duplicates.</p>
422      *
423      * @param value the new value.
424      *
425      *
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.
430      *
431      * @throws IllegalStateException if the collection is empty and is not a
432      * list with RECNO-RENUMBER access.
433      *
434      * @throws IllegalArgumentException if a duplicate value is being added
435      * that already exists and duplicates are sorted.
436      *
437      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
438      * including a {@code DatabaseException} on BDB (C Edition).
439      */
440     public void add(E value) {
441
442         coll.checkIterAddAllowed();
443         try {
444             OperationStatus status = OperationStatus.SUCCESS;
445             if (toNext != 0 && toPrevious != 0) { // database is empty
446                 if (coll.view.keysRenumbered) { // recno-renumber database
447                     /*
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.
452                      */
453                     close();
454                     status = coll.view.append(value, null, null);
455                     cursor = new DataCursor(coll.view, writeAllowed);
456                     reset();
457                     next(); // move past new record
458                 } else { // hash/btree with duplicates
459                     throw new IllegalStateException
460                         ("Collection is empty, cannot add() duplicate");
461                 }
462             } else { // database is not empty
463                 boolean putBefore = false;
464                 if (coll.view.keysRenumbered) { // recno-renumber database
465                     moveToCurrent();
466                     if (hasNext()) {
467                         status = cursor.putBefore(value);
468                         putBefore = true;
469                     } else {
470                         status = cursor.putAfter(value);
471                     }
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);
477                         putBefore = true;
478                     } else {
479                         status = cursor.putAfter(value);
480                     }
481                 }
482                 if (putBefore) {
483                     toPrevious = 0;
484                     toNext = MOVE_NEXT;
485                 }
486             }
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);
491             }
492             setAndRemoveAllowed = false;
493         } catch (Exception e) {
494             throw StoredContainer.convertException(e);
495         }
496     }
497
498     // --- end Iterator/ListIterator methods ---
499
500     /**
501      * Resets cursor to an uninitialized state.
502      */
503     private void reset() {
504
505         toNext = MOVE_FIRST;
506         toPrevious = MOVE_PREV;
507         toCurrent = 0;
508         currentData = null;
509         /*
510          * Initialize cursor at beginning to avoid "initial previous == last"
511          * behavior when cursor is uninitialized.
512          *
513          * FindBugs whines about us ignoring the return value from hasNext().
514          */
515         hasNext();
516     }
517
518     /**
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.
524      *
525      *
526      * @return the number of duplicates.
527      *
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().
531      */
532     public int count() {
533
534         if (!setAndRemoveAllowed) {
535             throw new IllegalStateException();
536         }
537         try {
538             moveToCurrent();
539             return cursor.count();
540         } catch (Exception e) {
541             throw StoredContainer.convertException(e);
542         }
543     }
544
545     /**
546      * Closes this iterator.
547      * This method does not exist in the standard {@link Iterator} or {@link
548      * ListIterator} interfaces.
549      *
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
554      * thrown.</p>
555      *
556      * @throws RuntimeExceptionWrapper if a checked exception is thrown,
557      * including a {@code DatabaseException} on BDB (C Edition).
558      */
559     public void close() {
560
561         if (cursor != null) {
562             coll.closeCursor(cursor);
563             cursor = null;
564         }
565     }
566
567     /**
568      * Returns the collection associated with this iterator.
569      * This method does not exist in the standard {@link Iterator} or {@link
570      * ListIterator} interfaces.
571      *
572      * @return the collection associated with this iterator.
573      */
574     public final StoredCollection<E> getCollection() {
575
576         return coll;
577     }
578
579     // --- begin BaseIterator methods ---
580
581     final ListIterator<E> dup() {
582
583         try {
584             StoredIterator o = (StoredIterator) super.clone();
585             o.cursor = cursor.cloneCursor();
586             return o;
587         } catch (Exception e) {
588             throw StoredContainer.convertException(e);
589         }
590     }
591
592     final boolean isCurrentData(Object currentData) {
593
594         return (this.currentData == currentData);
595     }
596
597     final boolean moveToIndex(int index) {
598
599         try {
600             OperationStatus status =
601                 cursor.getSearchKey(Integer.valueOf(index),
602                                     null, lockForWrite);
603             setAndRemoveAllowed = (status == OperationStatus.SUCCESS);
604             return setAndRemoveAllowed;
605         } catch (Exception e) {
606             throw StoredContainer.convertException(e);
607         }
608     }
609
610     // --- end BaseIterator methods ---
611
612     private void moveToCurrent()
613         throws DatabaseException {
614
615         if (toCurrent != 0) {
616             move(toCurrent);
617             toCurrent = 0;
618         }
619     }
620
621     private OperationStatus move(int direction)
622         throws DatabaseException {
623
624         switch (direction) {
625             case MOVE_NEXT:
626                 if (coll.iterateDuplicates()) {
627                     return cursor.getNext(lockForWrite);
628                 } else {
629                     return cursor.getNextNoDup(lockForWrite);
630                 }
631             case MOVE_PREV:
632                 if (coll.iterateDuplicates()) {
633                     return cursor.getPrev(lockForWrite);
634                 } else {
635                     return cursor.getPrevNoDup(lockForWrite);
636                 }
637             case MOVE_FIRST:
638                 return cursor.getFirst(lockForWrite);
639             default:
640                 throw DbCompat.unexpectedState(String.valueOf(direction));
641         }
642     }
643 }