• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-WNDR4500v2-V1.0.0.60_1.0.38/ap/gpl/timemachine/db-4.7.25.NC/java/src/com/sleepycat/collections/
1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000,2008 Oracle.  All rights reserved.
5 *
6 * $Id: BlockIterator.java,v 12.7 2008/02/07 17:12:26 mark Exp $
7 */
8
9package com.sleepycat.collections;
10
11import java.util.ListIterator;
12import java.util.NoSuchElementException;
13
14import com.sleepycat.compat.DbCompat;
15import com.sleepycat.db.DatabaseEntry;
16import com.sleepycat.db.DatabaseException;
17import com.sleepycat.db.OperationStatus;
18import com.sleepycat.util.keyrange.KeyRange;
19
20/**
21 * An iterator that does not need closing because a cursor is not kept open
22 * across method calls.  A cursor is opened to read a block of records at a
23 * time and then closed before the method returns.
24 *
25 * @author Mark Hayes
26 */
27class BlockIterator implements BaseIterator {
28
29    private StoredCollection coll;
30    private boolean writeAllowed;
31
32    /**
33     * Slots for a block of record keys and values.  The priKeys array is only
34     * used for secondary databases; otherwise it is set to the keys array.
35     */
36    private byte[][] keys;
37    private byte[][] priKeys;
38    private byte[][] values;
39
40    /**
41     * The slot index of the record that would be returned by next().
42     * nextIndex is always greater or equal to zero.  If the next record is not
43     * available, then nextIndex is equal to keys.length or keys[nextIndex] is
44     * null.
45     *
46     * If the block is empty, then either the iterator is uninitialized or the
47     * key range is empty.  Either way, nextIndex will be the array length and
48     * all array values will be null.  This is the initial state set by the
49     * constructor.  If remove() is used to delete all records in the key
50     * range, it will restore the iterator to this initial state.  The block
51     * must never be allowed to become empty when the key range is non-empty,
52     * since then the iterator's position would be lost.  [#15858]
53     */
54    private int nextIndex;
55
56    /**
57     * The slot index of the record last returned by next() or previous(), or
58     * the record inserted by add().  dataIndex is -1 if the data record is not
59     * available.  If greater or equal to zero, the slot at dataIndex is always
60     * non-null.
61     */
62    private int dataIndex;
63
64    /**
65     * The iterator data last returned by next() or previous().  This value is
66     * set to null if dataIndex is -1, or if the state of the iterator is such
67     * that set() or remove() cannot be called.  For example, after add() this
68     * field is set to null, even though the dataIndex is still valid.
69     */
70    private Object dataObject;
71
72    /**
73     * Creates an iterator.
74     */
75    BlockIterator(StoredCollection coll, boolean writeAllowed, int blockSize) {
76
77        this.coll = coll;
78        this.writeAllowed = writeAllowed;
79
80        keys = new byte[blockSize][];
81        priKeys = coll.isSecondary() ? (new byte[blockSize][]) : keys;
82        values = new byte[blockSize][];
83
84        nextIndex = blockSize;
85        dataIndex = -1;
86        dataObject = null;
87    }
88
89    /**
90     * Copy constructor.
91     */
92    private BlockIterator(BlockIterator o) {
93
94        coll = o.coll;
95        writeAllowed = o.writeAllowed;
96
97        keys = copyArray(o.keys);
98        priKeys = coll.isSecondary() ? copyArray(o.priKeys) : keys;
99        values = copyArray(o.values);
100
101        nextIndex = o.nextIndex;
102        dataIndex = o.dataIndex;
103        dataObject = o.dataObject;
104    }
105
106    /**
107     * Copies an array of byte arrays.
108     */
109    private byte[][] copyArray(byte[][] a) {
110
111        byte[][] b = new byte[a.length][];
112        for (int i = 0; i < b.length; i += 1) {
113            if (a[i] != null) {
114                b[i] = KeyRange.copyBytes(a[i]);
115            }
116        }
117        return b;
118    }
119
120    /**
121     * Returns whether the element at nextIndex is available.
122     */
123    private boolean isNextAvailable() {
124
125        return (nextIndex < keys.length) &&
126               (keys[nextIndex] != null);
127    }
128
129    /**
130     * Returns whether the element at nextIndex-1 is available.
131     */
132    private boolean isPrevAvailable() {
133
134        return (nextIndex > 0) &&
135               (keys[nextIndex - 1] != null);
136    }
137
138    /**
139     * Returns the record number at the given slot position.
140     */
141    private int getRecordNumber(int i) {
142
143        if (coll.view.btreeRecNumDb) {
144            DataCursor cursor = null;
145            try {
146                cursor = new DataCursor(coll.view, false);
147                if (moveCursor(i, cursor)) {
148                    return cursor.getCurrentRecordNumber();
149                } else {
150                    throw new IllegalStateException();
151                }
152            } catch (DatabaseException e) {
153                throw StoredContainer.convertException(e);
154            } finally {
155                closeCursor(cursor);
156            }
157        } else {
158            DatabaseEntry entry = new DatabaseEntry(keys[i]);
159            return DbCompat.getRecordNumber(entry);
160        }
161    }
162
163    /**
164     * Sets dataObject to the iterator data for the element at dataIndex.
165     */
166    private void makeDataObject() {
167
168        int i = dataIndex;
169        DatabaseEntry keyEntry = new DatabaseEntry(keys[i]);
170        DatabaseEntry priKeyEntry = (keys != priKeys)
171                                    ? (new DatabaseEntry(priKeys[i]))
172                                    : keyEntry;
173        DatabaseEntry valuesEntry = new DatabaseEntry(values[i]);
174
175        dataObject = coll.makeIteratorData(this, keyEntry, priKeyEntry,
176                                           valuesEntry);
177    }
178
179    /**
180     * Sets all slots to null.
181     */
182    private void clearSlots() {
183
184        for (int i = 0; i < keys.length; i += 1) {
185            keys[i] = null;
186            priKeys[i] = null;
187            values[i] = null;
188        }
189    }
190
191    /**
192     * Sets a given slot using the data in the given cursor.
193     */
194    private void setSlot(int i, DataCursor cursor) {
195
196        keys[i] = KeyRange.getByteArray(cursor.getKeyThang());
197
198        if (keys != priKeys) {
199            priKeys[i] = KeyRange.getByteArray
200                (cursor.getPrimaryKeyThang());
201        }
202
203        values[i] = KeyRange.getByteArray(cursor.getValueThang());
204    }
205
206    /**
207     * Inserts an added record at a given slot position and shifts other slots
208     * accordingly.  Also adjusts nextIndex and sets dataIndex to -1.
209     */
210    private void insertSlot(int i, DataCursor cursor) {
211
212        if (i < keys.length) {
213            for (int j = keys.length - 1; j > i; j -= 1) {
214
215                /* Shift right. */
216                keys[j] = keys[j - 1];
217                priKeys[j] = priKeys[j - 1];
218                values[j] = values[j - 1];
219
220                /* Bump key in recno-renumber database. */
221                if (coll.view.recNumRenumber && keys[j] != null) {
222                    bumpRecordNumber(j);
223                }
224            }
225            nextIndex += 1;
226        } else {
227            if (i != keys.length) {
228                throw new IllegalStateException();
229            }
230            i -= 1;
231            for (int j = 0; j < i; j += 1) {
232                /* Shift left. */
233                keys[j] = keys[j + 1];
234                priKeys[j] = priKeys[j + 1];
235                values[j] = values[j + 1];
236            }
237        }
238        setSlot(i, cursor);
239        dataIndex = -1;
240    }
241
242    /**
243     * Increments the record number key at the given slot.
244     */
245    private void bumpRecordNumber(int i) {
246
247        DatabaseEntry entry = new DatabaseEntry(keys[i]);
248        DbCompat.setRecordNumber(entry,
249                                 DbCompat.getRecordNumber(entry) + 1);
250        keys[i] = entry.getData();
251    }
252
253    /**
254     * Deletes the given slot, adjusts nextIndex and sets dataIndex to -1.
255     */
256    private void deleteSlot(int i) {
257
258        for (int j = i + 1; j < keys.length; j += 1) {
259            keys[j - 1] = keys[j];
260            priKeys[j - 1] = priKeys[j];
261            values[j - 1] = values[j];
262        }
263        int last = keys.length - 1;
264        keys[last] = null;
265        priKeys[last] = null;
266        values[last] = null;
267
268        if (nextIndex > i) {
269            nextIndex -= 1;
270        }
271        dataIndex = -1;
272    }
273
274    /**
275     * Moves the cursor to the key/data at the given slot, and returns false
276     * if the reposition (search) fails.
277     */
278    private boolean moveCursor(int i, DataCursor cursor)
279        throws DatabaseException {
280
281        return cursor.repositionExact(keys[i], priKeys[i], values[i], false);
282    }
283
284    /**
285     * Closes the given cursor if non-null.
286     */
287    private void closeCursor(DataCursor cursor) {
288
289        if (cursor != null) {
290            try {
291                cursor.close();
292            } catch (DatabaseException e) {
293                throw StoredContainer.convertException(e);
294            }
295        }
296    }
297
298    // --- begin Iterator/ListIterator methods ---
299
300    public boolean hasNext() {
301
302        if (isNextAvailable()) {
303            return true;
304        }
305        DataCursor cursor = null;
306        try {
307            cursor = new DataCursor(coll.view, writeAllowed);
308            int prev = nextIndex - 1;
309            boolean found = false;
310
311            if (keys[prev] == null) {
312                /* Get the first record for an uninitialized iterator. */
313                OperationStatus status = cursor.getFirst(false);
314                if (status == OperationStatus.SUCCESS) {
315                    found = true;
316                    nextIndex = 0;
317                }
318            } else {
319                /* Reposition to the last known key/data pair. */
320                int repos = cursor.repositionRange
321                    (keys[prev], priKeys[prev], values[prev], false);
322
323                if (repos == DataCursor.REPOS_EXACT) {
324
325                    /*
326                     * The last known key/data pair was found and will now be
327                     * in slot zero.
328                     */
329                    found = true;
330                    nextIndex = 1;
331
332                    /* The data object is now in slot zero or not available. */
333                    if (dataIndex == prev) {
334                        dataIndex = 0;
335                    } else {
336                        dataIndex = -1;
337                        dataObject = null;
338                    }
339                } else if (repos == DataCursor.REPOS_NEXT) {
340
341                    /*
342                     * The last known key/data pair was not found, but the
343                     * following record was found and it will be in slot zero.
344                     */
345                    found = true;
346                    nextIndex = 0;
347
348                    /* The data object is no longer available. */
349                    dataIndex = -1;
350                    dataObject = null;
351                } else {
352                    if (repos != DataCursor.REPOS_EOF) {
353                        throw new IllegalStateException();
354                    }
355                }
356            }
357
358            if (found) {
359                /* Clear all slots first in case an exception occurs below. */
360                clearSlots();
361
362                /* Attempt to fill all slots with records. */
363                int i = 0;
364                boolean done = false;
365                while (!done) {
366                    setSlot(i, cursor);
367                    i += 1;
368                    if (i < keys.length) {
369                        OperationStatus status = coll.iterateDuplicates() ?
370                                                 cursor.getNext(false) :
371                                                 cursor.getNextNoDup(false);
372                        if (status != OperationStatus.SUCCESS) {
373                            done = true;
374                        }
375                    } else {
376                        done = true;
377                    }
378                }
379
380            }
381
382            /*
383             * If REPOS_EXACT was returned above, make sure we retrieved
384             * the following record.
385             */
386            return isNextAvailable();
387        } catch (DatabaseException e) {
388            throw StoredContainer.convertException(e);
389        } finally {
390            closeCursor(cursor);
391        }
392    }
393
394    public boolean hasPrevious() {
395
396        if (isPrevAvailable()) {
397            return true;
398        }
399        if (!isNextAvailable()) {
400            return false;
401        }
402        DataCursor cursor = null;
403        try {
404            cursor = new DataCursor(coll.view, writeAllowed);
405            int last = keys.length - 1;
406            int next = nextIndex;
407            boolean found = false;
408
409            /* Reposition to the last known key/data pair. */
410            int repos = cursor.repositionRange
411                (keys[next], priKeys[next], values[next], false);
412
413            if (repos == DataCursor.REPOS_EXACT ||
414                repos == DataCursor.REPOS_NEXT) {
415
416                /*
417                 * The last known key/data pair, or the record following it,
418                 * was found and will now be in the last slot.
419                 */
420                found = true;
421                nextIndex = last;
422
423                /* The data object is now in the last slot or not available. */
424                if (dataIndex == next && repos == DataCursor.REPOS_EXACT) {
425                    dataIndex = last;
426                } else {
427                    dataIndex = -1;
428                    dataObject = null;
429                }
430            } else {
431                if (repos != DataCursor.REPOS_EOF) {
432                    throw new IllegalStateException();
433                }
434            }
435
436            if (found) {
437                /* Clear all slots first in case an exception occurs below. */
438                clearSlots();
439
440                /* Attempt to fill all slots with records. */
441                int i = last;
442                boolean done = false;
443                while (!done) {
444                    setSlot(i, cursor);
445                    i -= 1;
446                    if (i >= 0) {
447                        OperationStatus status = coll.iterateDuplicates() ?
448                                                 cursor.getPrev(false) :
449                                                 cursor.getPrevNoDup(false);
450                        if (status != OperationStatus.SUCCESS) {
451                            done = true;
452                        }
453                    } else {
454                        done = true;
455                    }
456                }
457            }
458
459            /*
460             * Make sure we retrieved the preceding record after the reposition
461             * above.
462             */
463            return isPrevAvailable();
464        } catch (DatabaseException e) {
465            throw StoredContainer.convertException(e);
466        } finally {
467            closeCursor(cursor);
468        }
469    }
470
471    public Object next() {
472
473        if (hasNext()) {
474            dataIndex = nextIndex;
475            nextIndex += 1;
476            makeDataObject();
477            return dataObject;
478        } else {
479            throw new NoSuchElementException();
480        }
481    }
482
483    public Object previous() {
484
485        if (hasPrevious()) {
486            nextIndex -= 1;
487            dataIndex = nextIndex;
488            makeDataObject();
489            return dataObject;
490        } else {
491            throw new NoSuchElementException();
492        }
493    }
494
495    public int nextIndex() {
496
497        if (!coll.view.recNumAccess) {
498            throw new UnsupportedOperationException(
499                "Record number access not supported");
500        }
501
502        return hasNext() ? (getRecordNumber(nextIndex) -
503                            coll.getIndexOffset())
504                         : Integer.MAX_VALUE;
505    }
506
507    public int previousIndex() {
508
509        if (!coll.view.recNumAccess) {
510            throw new UnsupportedOperationException(
511                "Record number access not supported");
512        }
513
514        return hasPrevious() ? (getRecordNumber(nextIndex - 1) -
515                                coll.getIndexOffset())
516                             : (-1);
517    }
518
519    public void set(Object value) {
520
521        if (dataObject == null) {
522            throw new IllegalStateException();
523        }
524        if (!coll.hasValues()) {
525            throw new UnsupportedOperationException();
526        }
527        DataCursor cursor = null;
528        boolean doAutoCommit = coll.beginAutoCommit();
529        try {
530            cursor = new DataCursor(coll.view, writeAllowed);
531            if (moveCursor(dataIndex, cursor)) {
532                cursor.putCurrent(value);
533                setSlot(dataIndex, cursor);
534                coll.closeCursor(cursor);
535                coll.commitAutoCommit(doAutoCommit);
536            } else {
537                throw new IllegalStateException();
538            }
539        } catch (Exception e) {
540            coll.closeCursor(cursor);
541            throw coll.handleException(e, doAutoCommit);
542        }
543    }
544
545    public void remove() {
546
547        if (dataObject == null) {
548            throw new IllegalStateException();
549        }
550        DataCursor cursor = null;
551        boolean doAutoCommit = coll.beginAutoCommit();
552        try {
553            cursor = new DataCursor(coll.view, writeAllowed);
554            if (moveCursor(dataIndex, cursor)) {
555                cursor.delete();
556                deleteSlot(dataIndex);
557                dataObject = null;
558
559                /*
560                 * Repopulate the block after removing all records, using the
561                 * cursor position of the deleted record as a starting point.
562                 * First try moving forward, since the user was moving forward.
563                 * (It is possible to delete all records in the block only by
564                 * moving forward, i.e, when nextIndex is greater than
565                 * dataIndex.)
566                 */
567                if (nextIndex == 0 && keys[0] == null) {
568                    OperationStatus status;
569                    for (int i = 0; i < keys.length; i += 1) {
570                        status = coll.iterateDuplicates() ?
571                                 cursor.getNext(false) :
572                                 cursor.getNextNoDup(false);
573                        if (status == OperationStatus.SUCCESS) {
574                            setSlot(i, cursor);
575                        } else {
576                            break;
577                        }
578                    }
579
580                    /*
581                     * If no records are found past the cursor position, try
582                     * moving backward.  If no records are found before the
583                     * cursor position, leave nextIndex set to keys.length,
584                     * which is the same as the initial iterator state and is
585                     * appropriate for an empty key range.
586                     */
587                    if (keys[0] == null) {
588                        nextIndex = keys.length;
589                        for (int i = nextIndex - 1; i >= 0; i -= 1) {
590                            status = coll.iterateDuplicates() ?
591                                     cursor.getPrev(false) :
592                                     cursor.getPrevNoDup(false);
593                            if (status == OperationStatus.SUCCESS) {
594                                setSlot(i, cursor);
595                            } else {
596                                break;
597                            }
598                        }
599                    }
600                }
601                coll.closeCursor(cursor);
602                coll.commitAutoCommit(doAutoCommit);
603            } else {
604                throw new IllegalStateException();
605            }
606        } catch (Exception e) {
607            coll.closeCursor(cursor);
608            throw coll.handleException(e, doAutoCommit);
609        }
610    }
611
612    public void add(Object value) {
613
614        /*
615         * The checkIterAddAllowed method ensures that one of the following two
616         * conditions holds and throws UnsupportedOperationException otherwise:
617         * 1- This is a list iterator for a recno-renumber database.
618         * 2- This is a collection iterator for a duplicates database.
619         */
620        coll.checkIterAddAllowed();
621        OperationStatus status = OperationStatus.SUCCESS;
622        DataCursor cursor = null;
623        boolean doAutoCommit = coll.beginAutoCommit();
624        try {
625            if (coll.view.keysRenumbered || !coll.areDuplicatesOrdered()) {
626
627                /*
628                 * This is a recno-renumber database or a btree/hash database
629                 * with unordered duplicates.
630                 */
631                boolean hasPrev = hasPrevious();
632                if (!hasPrev && !hasNext()) {
633
634                    /* The collection is empty. */
635                    if (coll.view.keysRenumbered) {
636
637                        /* Append to an empty recno-renumber database. */
638                        status = coll.view.append(value, null, null);
639
640                    } else if (coll.view.dupsAllowed &&
641                               coll.view.range.isSingleKey()) {
642
643                        /*
644                         * When inserting a duplicate into a single-key range,
645                         * the main key is fixed, so we can always insert into
646                         * an empty collection.
647                         */
648                        cursor = new DataCursor(coll.view, writeAllowed);
649                        cursor.useRangeKey();
650                        status = cursor.putNoDupData(null, value, null, true);
651                        coll.closeCursor(cursor);
652                        cursor = null;
653                    } else {
654                        throw new IllegalStateException
655                            ("Collection is empty, cannot add() duplicate");
656                    }
657
658                    /*
659                     * Move past the record just inserted (the cursor should be
660                     * closed above to prevent multiple open cursors in certain
661                     * DB core modes).
662                     */
663                    if (status == OperationStatus.SUCCESS) {
664                        next();
665                        dataIndex = nextIndex - 1;
666                    }
667                } else {
668
669                    /*
670                     * The collection is non-empty.  If hasPrev is true then
671                     * the element at (nextIndex - 1) is available; otherwise
672                     * the element at nextIndex is available.
673                     */
674                    cursor = new DataCursor(coll.view, writeAllowed);
675                    int insertIndex = hasPrev ? (nextIndex - 1) : nextIndex;
676
677                    if (!moveCursor(insertIndex, cursor)) {
678                        throw new IllegalStateException();
679                    }
680
681                    /*
682                     * For a recno-renumber database or a database with
683                     * unsorted duplicates, insert before the iterator 'next'
684                     * position, or after the 'prev' position.  Then adjust
685                     * the slots to account for the inserted record.
686                     */
687                    status = hasPrev ? cursor.putAfter(value)
688                                     : cursor.putBefore(value);
689                    if (status == OperationStatus.SUCCESS) {
690                        insertSlot(nextIndex, cursor);
691                    }
692                }
693            } else {
694                /* This is a btree/hash database with ordered duplicates. */
695                cursor = new DataCursor(coll.view, writeAllowed);
696
697                if (coll.view.range.isSingleKey()) {
698
699                    /*
700                     * When inserting a duplicate into a single-key range,
701                     * the main key is fixed.
702                     */
703                    cursor.useRangeKey();
704                } else {
705
706                    /*
707                     * When inserting into a multi-key range, the main key
708                     * is the last dataIndex accessed by next(), previous()
709                     * or add().
710                     */
711                    if (dataIndex < 0 || !moveCursor(dataIndex, cursor)) {
712                        throw new IllegalStateException();
713                    }
714                }
715
716                /*
717                 * For a hash/btree with duplicates, insert the duplicate,
718                 * put the new record in slot zero, and set the next index
719                 * to slot one (past the new record).
720                 */
721                status = cursor.putNoDupData(null, value, null, true);
722                if (status == OperationStatus.SUCCESS) {
723                    clearSlots();
724                    setSlot(0, cursor);
725                    dataIndex = 0;
726                    nextIndex = 1;
727                }
728            }
729
730            if (status == OperationStatus.KEYEXIST) {
731                throw new IllegalArgumentException("Duplicate value");
732            } else if (status != OperationStatus.SUCCESS) {
733                throw new IllegalArgumentException("Could not insert: " +
734                                                    status);
735            }
736
737            /* Prevent subsequent set() or remove() call. */
738            dataObject = null;
739
740            coll.closeCursor(cursor);
741            coll.commitAutoCommit(doAutoCommit);
742        } catch (Exception e) {
743            coll.closeCursor(cursor);
744            throw coll.handleException(e, doAutoCommit);
745        }
746    }
747
748    // --- end Iterator/ListIterator methods ---
749
750    // --- begin BaseIterator methods ---
751
752    public final ListIterator dup() {
753
754        return new BlockIterator(this);
755    }
756
757    public final boolean isCurrentData(Object currentData) {
758
759        return (dataObject == currentData);
760    }
761
762    public final boolean moveToIndex(int index) {
763
764        DataCursor cursor = null;
765        try {
766            cursor = new DataCursor(coll.view, writeAllowed);
767            OperationStatus status =
768                cursor.getSearchKey(new Integer(index), null, false);
769            if (status == OperationStatus.SUCCESS) {
770                clearSlots();
771                setSlot(0, cursor);
772                nextIndex = 0;
773                return true;
774            } else {
775                return false;
776            }
777        } catch (DatabaseException e) {
778            throw StoredContainer.convertException(e);
779        } finally {
780            closeCursor(cursor);
781        }
782    }
783
784    // --- end BaseIterator methods ---
785}
786