001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2000,2008 Oracle. All rights reserved.
005: *
006: * $Id: StoredIterator.java,v 1.45.2.5 2008/01/07 15:14:06 cwl Exp $
007: */
008:
009: package com.sleepycat.collections;
010:
011: import java.util.Iterator;
012: import java.util.ListIterator;
013: import java.util.NoSuchElementException;
014:
015: import com.sleepycat.je.DatabaseException;
016: import com.sleepycat.je.OperationStatus;
017: import com.sleepycat.util.RuntimeExceptionWrapper;
018:
019: /**
020: * The Iterator returned by all stored collections.
021: *
022: * <p>While in general this class conforms to the {@link Iterator} interface,
023: * it is important to note that all iterators for stored collections must be
024: * explicitly closed with {@link #close()}. The static method {@link
025: * #close(java.util.Iterator)} allows calling close for all iterators without
026: * harm to iterators that are not from stored collections, and also avoids
027: * casting. If a stored iterator is not closed, unpredictable behavior
028: * including process death may result.</p>
029: *
030: * <p>This class implements the {@link Iterator} interface for all stored
031: * iterators. It also implements {@link ListIterator} because some list
032: * iterator methods apply to all stored iterators, for example, {@link
033: * #previous} and {@link #hasPrevious}. Other list iterator methods are always
034: * supported for lists, but for other types of collections are only supported
035: * under certain conditions. See {@link #nextIndex}, {@link #previousIndex},
036: * {@link #add} and {@link #set} for details.</p>
037: *
038: * <p>In addition, this class provides the following methods for stored
039: * collection iterators only. Note that the use of these methods is not
040: * compatible with the standard Java collections interface.</p>
041: * <ul>
042: * <li>{@link #close()}</li>
043: * <li>{@link #close(Iterator)}</li>
044: * <li>{@link #count()}</li>
045: * <li>{@link #getCollection}</li>
046: * <li>{@link #setReadModifyWrite}</li>
047: * <li>{@link #isReadModifyWrite}</li>
048: * </ul>
049: *
050: * @author Mark Hayes
051: */
052: public class StoredIterator implements BaseIterator, Cloneable {
053:
054: /**
055: * Closes the given iterator using {@link #close()} if it is a {@link
056: * StoredIterator}. If the given iterator is not a {@link StoredIterator},
057: * this method does nothing.
058: *
059: * @param i is the iterator to close.
060: *
061: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is thrown.
062: */
063: public static void close(Iterator i) {
064:
065: if (i instanceof StoredIterator) {
066: ((StoredIterator) i).close();
067: }
068: }
069:
070: private static final int MOVE_NEXT = 1;
071: private static final int MOVE_PREV = 2;
072: private static final int MOVE_FIRST = 3;
073:
074: private boolean lockForWrite;
075: private StoredCollection coll;
076: private DataCursor cursor;
077: private int toNext;
078: private int toPrevious;
079: private int toCurrent;
080: private boolean writeAllowed;
081: private boolean setAndRemoveAllowed;
082: private Object currentData;
083:
084: StoredIterator(StoredCollection coll, boolean writeAllowed,
085: DataCursor joinCursor) {
086: try {
087: this .coll = coll;
088: this .writeAllowed = writeAllowed;
089: if (joinCursor == null)
090: this .cursor = new DataCursor(coll.view, writeAllowed);
091: else
092: this .cursor = joinCursor;
093: reset();
094: } catch (Exception e) {
095: try {
096: /* Ensure that the cursor is closed. [#10516] */
097: close();
098: } catch (Exception ignored) {
099: /* 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() - coll
282: .getIndexOffset()) : Integer.MAX_VALUE;
283: } catch (Exception e) {
284: throw StoredContainer.convertException(e);
285: }
286: }
287:
288: /**
289: * Returns the index of the element that would be returned by a subsequent
290: * call to previous.
291: * This method conforms to the {@link ListIterator#previousIndex}
292: * interface.
293: *
294: * @return the previous index.
295: *
296: * @throws UnsupportedOperationException if this iterator's collection does
297: * not use record number keys.
298: *
299: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
300: * thrown.
301: */
302: public int previousIndex() {
303:
304: if (!coll.view.recNumAccess) {
305: throw new UnsupportedOperationException(
306: "Record number access not supported");
307: }
308: try {
309: return hasPrevious() ? (cursor.getCurrentRecordNumber() - coll
310: .getIndexOffset())
311: : (-1);
312: } catch (Exception e) {
313: throw StoredContainer.convertException(e);
314: }
315: }
316:
317: /**
318: * Replaces the last element returned by next or previous with the
319: * specified element (optional operation).
320: * This method conforms to the {@link ListIterator#set} interface.
321: *
322: * <p>In order to call this method, if the underlying Database is
323: * transactional then a transaction must be active when creating the
324: * iterator.</p>
325: *
326: * @param value the new value.
327: *
328: * @throws UnsupportedOperationException if the collection is a {@link
329: * StoredKeySet} (the set returned by {@link java.util.Map#keySet}), or if
330: * duplicates are sorted since this would change the iterator position, or
331: * if the collection is indexed, or if the collection is read-only.
332: *
333: * @throws IllegalArgumentException if an entity value binding is used and
334: * the primary key of the value given is different than the existing stored
335: * primary key.
336: *
337: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
338: * thrown.
339: */
340: public void set(Object value) {
341:
342: if (!coll.hasValues())
343: throw new UnsupportedOperationException();
344: if (!setAndRemoveAllowed)
345: throw new IllegalStateException();
346: try {
347: moveToCurrent();
348: cursor.putCurrent(value);
349: } catch (Exception e) {
350: throw StoredContainer.convertException(e);
351: }
352: }
353:
354: /**
355: * Removes the last element that was returned by next or previous (optional
356: * operation).
357: * This method conforms to the {@link ListIterator#remove} interface except
358: * that when the collection is a list and the RECNO-RENUMBER access method
359: * is not used, list indices will not be renumbered.
360: *
361: * <p>In order to call this method, if the underlying Database is
362: * transactional then a transaction must be active when creating the
363: * iterator.</p>
364: *
365: * <p>Note that for the JE product, RECNO-RENUMBER databases are not
366: * supported, and therefore list indices are never renumbered by this
367: * method.</p>
368: *
369: * @throws UnsupportedOperationException if the collection is a sublist, or
370: * if the collection is read-only.
371: *
372: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
373: * thrown.
374: */
375: public void remove() {
376:
377: if (!setAndRemoveAllowed)
378: throw new IllegalStateException();
379: try {
380: moveToCurrent();
381: cursor.delete();
382: setAndRemoveAllowed = false;
383: toNext = MOVE_NEXT;
384: toPrevious = MOVE_PREV;
385: } catch (Exception e) {
386: throw StoredContainer.convertException(e);
387: }
388: }
389:
390: /**
391: * Inserts the specified element into the list or inserts a duplicate into
392: * other types of collections (optional operation).
393: * This method conforms to the {@link ListIterator#add} interface when
394: * the collection is a list and the RECNO-RENUMBER access method is used.
395: * Otherwise, this method may only be called when duplicates are allowed.
396: * If duplicates are unsorted, the new value will be inserted in the same
397: * manner as list elements.
398: * If duplicates are sorted, the new value will be inserted in sort order.
399: *
400: * <p>Note that for the JE product, RECNO-RENUMBER databases are not
401: * supported, and therefore this method may only be used to add
402: * duplicates.</p>
403: *
404: * @param value the new value.
405: *
406: * @throws UnsupportedOperationException if the collection is a sublist, or
407: * if the collection is indexed, or if the collection is read-only, or if
408: * the collection is a list and the RECNO-RENUMBER access method was not
409: * used, or if the collection is not a list and duplicates are not allowed.
410: *
411: * @throws IllegalStateException if the collection is empty and is not a
412: * list with RECNO-RENUMBER access.
413: *
414: * @throws IllegalArgumentException if a duplicate value is being added
415: * that already exists and duplicates are sorted.
416: *
417: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
418: * thrown.
419: */
420: public void add(Object value) {
421:
422: coll.checkIterAddAllowed();
423: try {
424: OperationStatus status = OperationStatus.SUCCESS;
425: if (toNext != 0 && toPrevious != 0) { // database is empty
426: if (coll.view.keysRenumbered) { // recno-renumber database
427: /*
428: * Close cursor during append and then reopen to support
429: * CDB restriction that append may not be called with a
430: * cursor open; note the append will still fail if the
431: * application has another cursor open.
432: */
433: close();
434: status = coll.view.append(value, null, null);
435: cursor = new DataCursor(coll.view, writeAllowed);
436: reset();
437: next(); // move past new record
438: } else { // hash/btree with duplicates
439: throw new IllegalStateException(
440: "Collection is empty, cannot add() duplicate");
441: }
442: } else { // database is not empty
443: boolean putBefore = false;
444: if (coll.view.keysRenumbered) { // recno-renumber database
445: moveToCurrent();
446: if (hasNext()) {
447: status = cursor.putBefore(value);
448: putBefore = true;
449: } else {
450: status = cursor.putAfter(value);
451: }
452: } else { // hash/btree with duplicates
453: if (coll.areDuplicatesOrdered()) {
454: status = cursor.putNoDupData(null, value, null,
455: true);
456: } else if (toNext == 0) {
457: status = cursor.putBefore(value);
458: putBefore = true;
459: } else {
460: status = cursor.putAfter(value);
461: }
462: }
463: if (putBefore) {
464: toPrevious = 0;
465: toNext = MOVE_NEXT;
466: }
467: }
468: if (status == OperationStatus.KEYEXIST) {
469: throw new IllegalArgumentException("Duplicate value");
470: } else if (status != OperationStatus.SUCCESS) {
471: throw new IllegalArgumentException("Could not insert: "
472: + status);
473: }
474: setAndRemoveAllowed = false;
475: } catch (Exception e) {
476: throw StoredContainer.convertException(e);
477: }
478: }
479:
480: // --- end Iterator/ListIterator methods ---
481:
482: /**
483: * Resets cursor to an uninitialized state.
484: */
485: private void reset() {
486:
487: toNext = MOVE_FIRST;
488: toPrevious = MOVE_PREV;
489: toCurrent = 0;
490: currentData = null;
491: /*
492: * Initialize cursor at beginning to avoid "initial previous == last"
493: * behavior when cursor is uninitialized.
494: *
495: * FindBugs whines about us ignoring the return value from hasNext().
496: */
497: hasNext();
498: }
499:
500: /**
501: * Returns the number of elements having the same key value as the key
502: * value of the element last returned by next() or previous(). If no
503: * duplicates are allowed, 1 is always returned.
504: * This method does not exist in the standard {@link Iterator} or {@link
505: * ListIterator} interfaces.
506: *
507: * @return the number of duplicates.
508: *
509: * @throws IllegalStateException if next() or previous() has not been
510: * called for this iterator, or if remove() or add() were called after
511: * the last call to next() or previous().
512: */
513: public int count() {
514:
515: if (!setAndRemoveAllowed)
516: throw new IllegalStateException();
517: try {
518: moveToCurrent();
519: return cursor.count();
520: } catch (Exception e) {
521: throw StoredContainer.convertException(e);
522: }
523: }
524:
525: /**
526: * Closes this iterator.
527: * This method does not exist in the standard {@link Iterator} or {@link
528: * ListIterator} interfaces.
529: *
530: * <p>After being closed, only the {@link #hasNext} and {@link
531: * #hasPrevious} methods may be called and these will return false. {@link
532: * #close()} may also be called again and will do nothing. If other
533: * methods are called a <code>NullPointerException</code> will generally be
534: * thrown.</p>
535: *
536: * @throws RuntimeExceptionWrapper if a {@link DatabaseException} is
537: * thrown.
538: */
539: public void close() {
540:
541: if (cursor != null) {
542: coll.closeCursor(cursor);
543: cursor = null;
544: }
545: }
546:
547: /**
548: * Returns the collection associated with this iterator.
549: * This method does not exist in the standard {@link Iterator} or {@link
550: * ListIterator} interfaces.
551: *
552: * @return the collection associated with this iterator.
553: */
554: public final StoredCollection getCollection() {
555:
556: return coll;
557: }
558:
559: // --- begin BaseIterator methods ---
560:
561: public final ListIterator dup() {
562:
563: try {
564: StoredIterator o = (StoredIterator) super .clone();
565: o.cursor = cursor.cloneCursor();
566: return o;
567: } catch (Exception e) {
568: throw StoredContainer.convertException(e);
569: }
570: }
571:
572: public final boolean isCurrentData(Object currentData) {
573:
574: return (this .currentData == currentData);
575: }
576:
577: public final boolean moveToIndex(int index) {
578:
579: try {
580: OperationStatus status = cursor.getSearchKey(new Integer(
581: index), null, lockForWrite);
582: setAndRemoveAllowed = (status == OperationStatus.SUCCESS);
583: return setAndRemoveAllowed;
584: } catch (Exception e) {
585: throw StoredContainer.convertException(e);
586: }
587: }
588:
589: // --- end BaseIterator methods ---
590:
591: private void moveToCurrent() throws DatabaseException {
592:
593: if (toCurrent != 0) {
594: move(toCurrent);
595: toCurrent = 0;
596: }
597: }
598:
599: private OperationStatus move(int direction)
600: throws DatabaseException {
601:
602: switch (direction) {
603: case MOVE_NEXT:
604: if (coll.iterateDuplicates()) {
605: return cursor.getNext(lockForWrite);
606: } else {
607: return cursor.getNextNoDup(lockForWrite);
608: }
609: case MOVE_PREV:
610: if (coll.iterateDuplicates()) {
611: return cursor.getPrev(lockForWrite);
612: } else {
613: return cursor.getPrevNoDup(lockForWrite);
614: }
615: case MOVE_FIRST:
616: return cursor.getFirst(lockForWrite);
617: default:
618: throw new IllegalArgumentException(String
619: .valueOf(direction));
620: }
621: }
622: }
|