1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: RangeCursor.java,v 1.8 2008/02/07 17:12:29 mark Exp $
7 */
8
9package com.sleepycat.util.keyrange;
10
11import com.sleepycat.compat.DbCompat;
12import com.sleepycat.db.Cursor;
13import com.sleepycat.db.DatabaseEntry;
14import com.sleepycat.db.DatabaseException;
15import com.sleepycat.db.LockMode;
16import com.sleepycat.db.OperationStatus;
17import com.sleepycat.db.SecondaryCursor;
18
19/**
20 * A cursor-like interface that enforces a key range.  The method signatures
21 * are actually those of SecondaryCursor, but the pKey parameter may be null.
22 * It was done this way to avoid doubling the number of methods.
23 *
24 * <p>This is not a fully general implementation of a range cursor and should
25 * not be used directly by applications; however, it may evolve into a
26 * generally useful range cursor some day.</p>
27 *
28 * @author Mark Hayes
29 */
30public class RangeCursor implements Cloneable {
31
32    /**
33     * The cursor and secondary cursor are the same object.  The secCursor is
34     * null if the database is not a secondary database.
35     */
36    private Cursor cursor;
37    private SecondaryCursor secCursor;
38
39    /**
40     * The range is always non-null, but may be unbounded meaning that it is
41     * open and not used.
42     */
43    private KeyRange range;
44
45    /**
46     * The pkRange may be non-null only if the range is a single-key range
47     * and the cursor is a secondary cursor.  It further restricts the range of
48     * primary keys in a secondary database.
49     */
50    private KeyRange pkRange;
51
52    /**
53     * If the DB supported sorted duplicates, then calling
54     * Cursor.getSearchBothRange is allowed.
55     */
56    private boolean sortedDups;
57
58    /**
59     * The privXxx entries are used only when the range is bounded.  We read
60     * into these private entries to avoid modifying the caller's entry
61     * parameters in the case where we read successfully but the key is out of
62     * range.  In that case we return NOTFOUND and we want to leave the entry
63     * parameters unchanged.
64     */
65    private DatabaseEntry privKey;
66    private DatabaseEntry privPKey;
67    private DatabaseEntry privData;
68
69    /**
70     * The initialized flag is set to true whenever we successfully position
71     * the cursor.  It is used to implement the getNext/Prev logic for doing a
72     * getFirst/Last when the cursor is not initialized.  We can't rely on
73     * Cursor to do that for us, since if we position the underlying cursor
74     * successfully but the key is out of range, we have no way to set the
75     * underlying cursor to uninitialized.  A range cursor always starts in the
76     * uninitialized state.
77     */
78    private boolean initialized;
79
80    /**
81     * Creates a range cursor with a duplicate range.
82     */
83    public RangeCursor(KeyRange range,
84                       KeyRange pkRange,
85                       boolean sortedDups,
86                       Cursor cursor)
87        throws DatabaseException {
88
89        if (pkRange != null && !range.singleKey) {
90            throw new IllegalArgumentException();
91        }
92        this.range = range;
93        this.pkRange = pkRange;
94        this.sortedDups = sortedDups;
95        this.cursor = cursor;
96        init();
97        if (pkRange != null && secCursor == null) {
98            throw new IllegalArgumentException();
99        }
100    }
101
102    /**
103     * Create a cloned range cursor.  The caller must clone the underlying
104     * cursor before using this constructor, because cursor open/close is
105     * handled specially for CDS cursors outside this class.
106     */
107    public RangeCursor dup(boolean samePosition)
108        throws DatabaseException {
109
110        try {
111            RangeCursor c = (RangeCursor) super.clone();
112            c.cursor = dupCursor(cursor, samePosition);
113            c.init();
114            return c;
115        } catch (CloneNotSupportedException neverHappens) {
116            return null;
117        }
118    }
119
120    /**
121     * Used for opening and duping (cloning).
122     */
123    private void init() {
124
125        if (cursor instanceof SecondaryCursor) {
126            secCursor = (SecondaryCursor) cursor;
127        } else {
128            secCursor = null;
129        }
130
131        if (range.hasBound()) {
132            privKey = new DatabaseEntry();
133            privPKey = new DatabaseEntry();
134            privData = new DatabaseEntry();
135        } else {
136            privKey = null;
137            privPKey = null;
138            privData = null;
139        }
140    }
141
142    /**
143     * Returns whether the cursor is initialized at a valid position.
144     */
145    public boolean isInitialized() {
146        return initialized;
147    }
148
149    /**
150     * Returns the underlying cursor.  Used for cloning.
151     */
152    public Cursor getCursor() {
153        return cursor;
154    }
155
156    /**
157     * When an unbounded range is used, this method is called to use the
158     * callers entry parameters directly, to avoid the extra step of copying
159     * between the private entries and the caller's entries.
160     */
161    private void setParams(DatabaseEntry key, DatabaseEntry pKey,
162                           DatabaseEntry data) {
163        privKey = key;
164        privPKey = pKey;
165        privData = data;
166    }
167
168    /**
169     * Dups the cursor, sets the cursor and secCursor fields to the duped
170     * cursor, and returns the old cursor.  Always call endOperation in a
171     * finally clause after calling beginOperation.
172     *
173     * <p>If the returned cursor == the cursor field, the cursor is
174     * uninitialized and was not duped; this case is handled correctly by
175     * endOperation.</p>
176     */
177    private Cursor beginOperation()
178        throws DatabaseException {
179
180        Cursor oldCursor = cursor;
181        if (initialized) {
182            cursor = dupCursor(cursor, true);
183            if (secCursor != null) {
184                secCursor = (SecondaryCursor) cursor;
185            }
186        } else {
187            return cursor;
188        }
189        return oldCursor;
190    }
191
192    /**
193     * If the operation succeded, leaves the duped cursor in place and closes
194     * the oldCursor.  If the operation failed, moves the oldCursor back in
195     * place and closes the duped cursor.  oldCursor may be null if
196     * beginOperation was not called, in cases where we don't need to dup
197     * the cursor.  Always call endOperation when a successful operation ends,
198     * in order to set the initialized field.
199     */
200    private void endOperation(Cursor oldCursor, OperationStatus status,
201                              DatabaseEntry key, DatabaseEntry pKey,
202                              DatabaseEntry data)
203        throws DatabaseException {
204
205        if (status == OperationStatus.SUCCESS) {
206            if (oldCursor != null && oldCursor != cursor) {
207                closeCursor(oldCursor);
208            }
209            if (key != null) {
210                swapData(key, privKey);
211            }
212            if (pKey != null && secCursor != null) {
213                swapData(pKey, privPKey);
214            }
215            if (data != null) {
216                swapData(data, privData);
217            }
218            initialized = true;
219        } else {
220            if (oldCursor != null && oldCursor != cursor) {
221                closeCursor(cursor);
222                cursor = oldCursor;
223                if (secCursor != null) {
224                    secCursor = (SecondaryCursor) cursor;
225                }
226            }
227        }
228    }
229
230    /**
231     * Swaps the contents of the two entries.  Used to return entry data to
232     * the caller when the operation was successful.
233     */
234    private static void swapData(DatabaseEntry e1, DatabaseEntry e2) {
235
236        byte[] d1 = e1.getData();
237        int o1 = e1.getOffset();
238        int s1 = e1.getSize();
239
240        e1.setData(e2.getData(), e2.getOffset(), e2.getSize());
241        e2.setData(d1, o1, s1);
242    }
243
244    /**
245     * Shares the same byte array, offset and size between two entries.
246     * Used when copying the entry data is not necessary because it is known
247     * that the underlying operation will not modify the entry, for example,
248     * with getSearchKey.
249     */
250    private static void shareData(DatabaseEntry from, DatabaseEntry to) {
251
252        if (from != null) {
253            to.setData(from.getData(), from.getOffset(), from.getSize());
254        }
255    }
256
257    public OperationStatus getFirst(DatabaseEntry key,
258                                    DatabaseEntry pKey,
259                                    DatabaseEntry data,
260                                    LockMode lockMode)
261        throws DatabaseException {
262
263        OperationStatus status;
264        if (!range.hasBound()) {
265            setParams(key, pKey, data);
266            status = doGetFirst(lockMode);
267            endOperation(null, status, null, null, null);
268            return status;
269        }
270        if (pkRange != null) {
271            KeyRange.copy(range.beginKey, privKey);
272            if (pkRange.singleKey) {
273                KeyRange.copy(pkRange.beginKey, privPKey);
274                status = doGetSearchBoth(lockMode);
275                endOperation(null, status, key, pKey, data);
276            } else {
277                status = OperationStatus.NOTFOUND;
278                Cursor oldCursor = beginOperation();
279                try {
280                    if (pkRange.beginKey == null || !sortedDups) {
281                        status = doGetSearchKey(lockMode);
282                    } else {
283                        KeyRange.copy(pkRange.beginKey, privPKey);
284                        status = doGetSearchBothRange(lockMode);
285                        if (status == OperationStatus.SUCCESS &&
286                            !pkRange.beginInclusive &&
287                            pkRange.compare(privPKey, pkRange.beginKey) == 0) {
288                            status = doGetNextDup(lockMode);
289                        }
290                    }
291                    if (status == OperationStatus.SUCCESS &&
292                        !pkRange.check(privPKey)) {
293                        status = OperationStatus.NOTFOUND;
294                    }
295                } finally {
296                    endOperation(oldCursor, status, key, pKey, data);
297                }
298            }
299        } else if (range.singleKey) {
300            KeyRange.copy(range.beginKey, privKey);
301            status = doGetSearchKey(lockMode);
302            endOperation(null, status, key, pKey, data);
303        } else {
304            status = OperationStatus.NOTFOUND;
305            Cursor oldCursor = beginOperation();
306            try {
307                if (range.beginKey == null) {
308                    status = doGetFirst(lockMode);
309                } else {
310                    KeyRange.copy(range.beginKey, privKey);
311                    status = doGetSearchKeyRange(lockMode);
312                    if (status == OperationStatus.SUCCESS &&
313                        !range.beginInclusive &&
314                        range.compare(privKey, range.beginKey) == 0) {
315                        status = doGetNextNoDup(lockMode);
316                    }
317                }
318                if (status == OperationStatus.SUCCESS &&
319                    !range.check(privKey)) {
320                    status = OperationStatus.NOTFOUND;
321                }
322            } finally {
323                endOperation(oldCursor, status, key, pKey, data);
324            }
325        }
326        return status;
327    }
328
329    public OperationStatus getLast(DatabaseEntry key,
330                                   DatabaseEntry pKey,
331                                   DatabaseEntry data,
332                                   LockMode lockMode)
333        throws DatabaseException {
334
335        OperationStatus status = OperationStatus.NOTFOUND;
336        if (!range.hasBound()) {
337            setParams(key, pKey, data);
338            status = doGetLast(lockMode);
339            endOperation(null, status, null, null, null);
340            return status;
341        }
342        Cursor oldCursor = beginOperation();
343        try {
344            if (pkRange != null) {
345                KeyRange.copy(range.beginKey, privKey);
346                boolean doLast = false;
347                if (!sortedDups) {
348                    status = doGetSearchKey(lockMode);
349                } else if (pkRange.endKey == null) {
350                    doLast = true;
351                } else {
352                    KeyRange.copy(pkRange.endKey, privPKey);
353                    status = doGetSearchBothRange(lockMode);
354                    if (status == OperationStatus.SUCCESS) {
355                        if (!pkRange.endInclusive ||
356                            pkRange.compare(pkRange.endKey, privPKey) != 0) {
357                            status = doGetPrevDup(lockMode);
358                        }
359                    } else {
360                        KeyRange.copy(range.beginKey, privKey);
361                        doLast = true;
362                    }
363                }
364                if (doLast) {
365                    status = doGetSearchKey(lockMode);
366                    if (status == OperationStatus.SUCCESS) {
367                        status = doGetNextNoDup(lockMode);
368                        if (status == OperationStatus.SUCCESS) {
369                            status = doGetPrev(lockMode);
370                        } else {
371                            status = doGetLast(lockMode);
372                        }
373                    }
374                }
375                if (status == OperationStatus.SUCCESS &&
376                    !pkRange.check(privPKey)) {
377                    status = OperationStatus.NOTFOUND;
378                }
379            } else if (range.endKey == null) {
380                status = doGetLast(lockMode);
381            } else {
382                KeyRange.copy(range.endKey, privKey);
383                status = doGetSearchKeyRange(lockMode);
384                if (status == OperationStatus.SUCCESS) {
385                    if (range.endInclusive &&
386                        range.compare(range.endKey, privKey) == 0) {
387                        /* Skip this step if dups are not configured? */
388                        status = doGetNextNoDup(lockMode);
389                        if (status == OperationStatus.SUCCESS) {
390                            status = doGetPrev(lockMode);
391                        } else {
392                            status = doGetLast(lockMode);
393                        }
394                    } else {
395                        status = doGetPrev(lockMode);
396                    }
397                } else {
398                    status = doGetLast(lockMode);
399                }
400            }
401            if (status == OperationStatus.SUCCESS &&
402                !range.checkBegin(privKey, true)) {
403                status = OperationStatus.NOTFOUND;
404            }
405        } finally {
406            endOperation(oldCursor, status, key, pKey, data);
407        }
408        return status;
409    }
410
411    public OperationStatus getNext(DatabaseEntry key,
412                                   DatabaseEntry pKey,
413                                   DatabaseEntry data,
414                                   LockMode lockMode)
415        throws DatabaseException {
416
417        OperationStatus status;
418        if (!initialized) {
419            return getFirst(key, pKey, data, lockMode);
420        }
421        if (!range.hasBound()) {
422            setParams(key, pKey, data);
423            status = doGetNext(lockMode);
424            endOperation(null, status, null, null, null);
425            return status;
426        }
427        if (pkRange != null) {
428            if (pkRange.endKey == null) {
429                status = doGetNextDup(lockMode);
430                endOperation(null, status, key, pKey, data);
431            } else {
432                status = OperationStatus.NOTFOUND;
433                Cursor oldCursor = beginOperation();
434                try {
435                    status = doGetNextDup(lockMode);
436                    if (status == OperationStatus.SUCCESS &&
437                        !pkRange.checkEnd(privPKey, true)) {
438                        status = OperationStatus.NOTFOUND;
439                    }
440                } finally {
441                    endOperation(oldCursor, status, key, pKey, data);
442                }
443            }
444        } else if (range.singleKey) {
445            status = doGetNextDup(lockMode);
446            endOperation(null, status, key, pKey, data);
447        } else {
448            status = OperationStatus.NOTFOUND;
449            Cursor oldCursor = beginOperation();
450            try {
451                status = doGetNext(lockMode);
452                if (status == OperationStatus.SUCCESS &&
453                    !range.check(privKey)) {
454                    status = OperationStatus.NOTFOUND;
455                }
456            } finally {
457                endOperation(oldCursor, status, key, pKey, data);
458            }
459        }
460        return status;
461    }
462
463    public OperationStatus getNextNoDup(DatabaseEntry key,
464                                        DatabaseEntry pKey,
465                                        DatabaseEntry data,
466                                        LockMode lockMode)
467        throws DatabaseException {
468
469        OperationStatus status;
470        if (!initialized) {
471            return getFirst(key, pKey, data, lockMode);
472        }
473        if (!range.hasBound()) {
474            setParams(key, pKey, data);
475            status = doGetNextNoDup(lockMode);
476            endOperation(null, status, null, null, null);
477            return status;
478        }
479        if (range.singleKey) {
480            status = OperationStatus.NOTFOUND;
481        } else {
482            status = OperationStatus.NOTFOUND;
483            Cursor oldCursor = beginOperation();
484            try {
485                status = doGetNextNoDup(lockMode);
486                if (status == OperationStatus.SUCCESS &&
487                    !range.check(privKey)) {
488                    status = OperationStatus.NOTFOUND;
489                }
490            } finally {
491                endOperation(oldCursor, status, key, pKey, data);
492            }
493        }
494        return status;
495    }
496
497    public OperationStatus getPrev(DatabaseEntry key,
498                                   DatabaseEntry pKey,
499                                   DatabaseEntry data,
500                                   LockMode lockMode)
501        throws DatabaseException {
502
503        OperationStatus status;
504        if (!initialized) {
505            return getLast(key, pKey, data, lockMode);
506        }
507        if (!range.hasBound()) {
508            setParams(key, pKey, data);
509            status = doGetPrev(lockMode);
510            endOperation(null, status, null, null, null);
511            return status;
512        }
513        if (pkRange != null) {
514            if (pkRange.beginKey == null) {
515                status = doGetPrevDup(lockMode);
516                endOperation(null, status, key, pKey, data);
517            } else {
518                status = OperationStatus.NOTFOUND;
519                Cursor oldCursor = beginOperation();
520                try {
521                    status = doGetPrevDup(lockMode);
522                    if (status == OperationStatus.SUCCESS &&
523                        !pkRange.checkBegin(privPKey, true)) {
524                        status = OperationStatus.NOTFOUND;
525                    }
526                } finally {
527                    endOperation(oldCursor, status, key, pKey, data);
528                }
529            }
530        } else if (range.singleKey) {
531            status = doGetPrevDup(lockMode);
532            endOperation(null, status, key, pKey, data);
533        } else {
534            status = OperationStatus.NOTFOUND;
535            Cursor oldCursor = beginOperation();
536            try {
537                status = doGetPrev(lockMode);
538                if (status == OperationStatus.SUCCESS &&
539                    !range.check(privKey)) {
540                    status = OperationStatus.NOTFOUND;
541                }
542            } finally {
543                endOperation(oldCursor, status, key, pKey, data);
544            }
545        }
546        return status;
547    }
548
549    public OperationStatus getPrevNoDup(DatabaseEntry key,
550                                        DatabaseEntry pKey,
551                                        DatabaseEntry data,
552                                        LockMode lockMode)
553        throws DatabaseException {
554
555        OperationStatus status;
556        if (!initialized) {
557            return getLast(key, pKey, data, lockMode);
558        }
559        if (!range.hasBound()) {
560            setParams(key, pKey, data);
561            status = doGetPrevNoDup(lockMode);
562            endOperation(null, status, null, null, null);
563            return status;
564        }
565        if (range.singleKey) {
566            status = OperationStatus.NOTFOUND;
567        } else {
568            status = OperationStatus.NOTFOUND;
569            Cursor oldCursor = beginOperation();
570            try {
571                status = doGetPrevNoDup(lockMode);
572                if (status == OperationStatus.SUCCESS &&
573                    !range.check(privKey)) {
574                    status = OperationStatus.NOTFOUND;
575                }
576            } finally {
577                endOperation(oldCursor, status, key, pKey, data);
578            }
579        }
580        return status;
581    }
582
583    public OperationStatus getSearchKey(DatabaseEntry key,
584                                        DatabaseEntry pKey,
585                                        DatabaseEntry data,
586                                        LockMode lockMode)
587        throws DatabaseException {
588
589        OperationStatus status;
590        if (!range.hasBound()) {
591            setParams(key, pKey, data);
592            status = doGetSearchKey(lockMode);
593            endOperation(null, status, null, null, null);
594            return status;
595        }
596        if (!range.check(key)) {
597            status = OperationStatus.NOTFOUND;
598        } else if (pkRange != null) {
599            status = OperationStatus.NOTFOUND;
600            Cursor oldCursor = beginOperation();
601            try {
602                shareData(key, privKey);
603                status = doGetSearchKey(lockMode);
604                if (status == OperationStatus.SUCCESS &&
605                    !pkRange.check(privPKey)) {
606                    status = OperationStatus.NOTFOUND;
607                }
608            } finally {
609                endOperation(oldCursor, status, key, pKey, data);
610            }
611        } else {
612            shareData(key, privKey);
613            status = doGetSearchKey(lockMode);
614            endOperation(null, status, key, pKey, data);
615        }
616        return status;
617    }
618
619    public OperationStatus getSearchBoth(DatabaseEntry key,
620                                         DatabaseEntry pKey,
621                                         DatabaseEntry data,
622                                         LockMode lockMode)
623        throws DatabaseException {
624
625        OperationStatus status;
626        if (!range.hasBound()) {
627            setParams(key, pKey, data);
628            status = doGetSearchBoth(lockMode);
629            endOperation(null, status, null, null, null);
630            return status;
631        }
632        if (!range.check(key) ||
633            (pkRange != null && !pkRange.check(pKey))) {
634            status = OperationStatus.NOTFOUND;
635        } else {
636            shareData(key, privKey);
637            if (secCursor != null) {
638                shareData(pKey, privPKey);
639            } else {
640                shareData(data, privData);
641            }
642            status = doGetSearchBoth(lockMode);
643            endOperation(null, status, key, pKey, data);
644        }
645        return status;
646    }
647
648    public OperationStatus getSearchKeyRange(DatabaseEntry key,
649                                             DatabaseEntry pKey,
650                                             DatabaseEntry data,
651                                             LockMode lockMode)
652        throws DatabaseException {
653
654        OperationStatus status = OperationStatus.NOTFOUND;
655        if (!range.hasBound()) {
656            setParams(key, pKey, data);
657            status = doGetSearchKeyRange(lockMode);
658            endOperation(null, status, null, null, null);
659            return status;
660        }
661        Cursor oldCursor = beginOperation();
662        try {
663            shareData(key, privKey);
664            status = doGetSearchKeyRange(lockMode);
665            if (status == OperationStatus.SUCCESS &&
666                (!range.check(privKey) ||
667                 (pkRange != null && !pkRange.check(pKey)))) {
668                status = OperationStatus.NOTFOUND;
669            }
670        } finally {
671            endOperation(oldCursor, status, key, pKey, data);
672        }
673        return status;
674    }
675
676    public OperationStatus getSearchBothRange(DatabaseEntry key,
677                                              DatabaseEntry pKey,
678                                              DatabaseEntry data,
679                                              LockMode lockMode)
680        throws DatabaseException {
681
682        OperationStatus status = OperationStatus.NOTFOUND;
683        if (!range.hasBound()) {
684            setParams(key, pKey, data);
685            status = doGetSearchBothRange(lockMode);
686            endOperation(null, status, null, null, null);
687            return status;
688        }
689        Cursor oldCursor = beginOperation();
690        try {
691            shareData(key, privKey);
692            if (secCursor != null) {
693                shareData(pKey, privPKey);
694            } else {
695                shareData(data, privData);
696            }
697            status = doGetSearchBothRange(lockMode);
698            if (status == OperationStatus.SUCCESS &&
699                (!range.check(privKey) ||
700                 (pkRange != null && !pkRange.check(pKey)))) {
701                status = OperationStatus.NOTFOUND;
702            }
703        } finally {
704            endOperation(oldCursor, status, key, pKey, data);
705        }
706        return status;
707    }
708
709    public OperationStatus getSearchRecordNumber(DatabaseEntry key,
710                                                 DatabaseEntry pKey,
711                                                 DatabaseEntry data,
712                                                 LockMode lockMode)
713        throws DatabaseException {
714
715        OperationStatus status;
716        if (!range.hasBound()) {
717            setParams(key, pKey, data);
718            status = doGetSearchRecordNumber(lockMode);
719            endOperation(null, status, null, null, null);
720            return status;
721        }
722        if (!range.check(key)) {
723            status = OperationStatus.NOTFOUND;
724        } else {
725            shareData(key, privKey);
726            status = doGetSearchRecordNumber(lockMode);
727            endOperation(null, status, key, pKey, data);
728        }
729        return status;
730    }
731
732    public OperationStatus getNextDup(DatabaseEntry key,
733                                      DatabaseEntry pKey,
734                                      DatabaseEntry data,
735                                      LockMode lockMode)
736        throws DatabaseException {
737
738        if (!initialized) {
739            throw new DatabaseException("Cursor not initialized");
740        }
741        OperationStatus status;
742        if (!range.hasBound()) {
743            setParams(key, pKey, data);
744            status = doGetNextDup(lockMode);
745            endOperation(null, status, null, null, null);
746        } else if (pkRange != null && pkRange.endKey != null) {
747            status = OperationStatus.NOTFOUND;
748            Cursor oldCursor = beginOperation();
749            try {
750                status = doGetNextDup(lockMode);
751                if (status == OperationStatus.SUCCESS &&
752                    !pkRange.checkEnd(privPKey, true)) {
753                    status = OperationStatus.NOTFOUND;
754                }
755            } finally {
756                endOperation(oldCursor, status, key, pKey, data);
757            }
758        } else {
759            status = doGetNextDup(lockMode);
760            endOperation(null, status, key, pKey, data);
761        }
762        return status;
763    }
764
765    public OperationStatus getPrevDup(DatabaseEntry key,
766                                      DatabaseEntry pKey,
767                                      DatabaseEntry data,
768                                      LockMode lockMode)
769        throws DatabaseException {
770
771        if (!initialized) {
772            throw new DatabaseException("Cursor not initialized");
773        }
774        OperationStatus status;
775        if (!range.hasBound()) {
776            setParams(key, pKey, data);
777            status = doGetPrevDup(lockMode);
778            endOperation(null, status, null, null, null);
779        } else if (pkRange != null && pkRange.beginKey != null) {
780            status = OperationStatus.NOTFOUND;
781            Cursor oldCursor = beginOperation();
782            try {
783                status = doGetPrevDup(lockMode);
784                if (status == OperationStatus.SUCCESS &&
785                    !pkRange.checkBegin(privPKey, true)) {
786                    status = OperationStatus.NOTFOUND;
787                }
788            } finally {
789                endOperation(oldCursor, status, key, pKey, data);
790            }
791        } else {
792            status = doGetPrevDup(lockMode);
793            endOperation(null, status, key, pKey, data);
794        }
795        return status;
796    }
797
798    public OperationStatus getCurrent(DatabaseEntry key,
799                                      DatabaseEntry pKey,
800                                      DatabaseEntry data,
801                                      LockMode lockMode)
802        throws DatabaseException {
803
804        if (!initialized) {
805            throw new DatabaseException("Cursor not initialized");
806        }
807        if (secCursor != null && pKey != null) {
808            return secCursor.getCurrent(key, pKey, data, lockMode);
809        } else {
810            return cursor.getCurrent(key, data, lockMode);
811        }
812    }
813
814    /*
815     * Pass-thru methods.
816     */
817
818    public void close()
819        throws DatabaseException {
820
821        closeCursor(cursor);
822    }
823
824    public int count()
825        throws DatabaseException {
826
827	return cursor.count();
828    }
829
830    public OperationStatus delete()
831        throws DatabaseException {
832
833	return cursor.delete();
834    }
835
836    public OperationStatus put(DatabaseEntry key, DatabaseEntry data)
837        throws DatabaseException {
838
839        return cursor.put(key, data);
840    }
841
842    public OperationStatus putNoOverwrite(DatabaseEntry key,
843                                          DatabaseEntry data)
844        throws DatabaseException {
845
846        return cursor.putNoOverwrite(key, data);
847    }
848
849    public OperationStatus putNoDupData(DatabaseEntry key, DatabaseEntry data)
850        throws DatabaseException {
851
852        return cursor.putNoDupData(key, data);
853    }
854
855    public OperationStatus putCurrent(DatabaseEntry data)
856        throws DatabaseException {
857
858        return cursor.putCurrent(data);
859    }
860
861    public OperationStatus putAfter(DatabaseEntry key, DatabaseEntry data)
862        throws DatabaseException {
863
864        return DbCompat.putAfter(cursor, key, data);
865    }
866
867    public OperationStatus putBefore(DatabaseEntry key, DatabaseEntry data)
868        throws DatabaseException {
869
870        return DbCompat.putBefore(cursor, key, data);
871    }
872
873    private OperationStatus doGetFirst(LockMode lockMode)
874        throws DatabaseException {
875
876        if (secCursor != null && privPKey != null) {
877            return secCursor.getFirst(privKey, privPKey, privData, lockMode);
878        } else {
879            return cursor.getFirst(privKey, privData, lockMode);
880        }
881    }
882
883    private OperationStatus doGetLast(LockMode lockMode)
884        throws DatabaseException {
885
886        if (secCursor != null && privPKey != null) {
887            return secCursor.getLast(privKey, privPKey, privData, lockMode);
888        } else {
889            return cursor.getLast(privKey, privData, lockMode);
890        }
891    }
892
893    private OperationStatus doGetNext(LockMode lockMode)
894        throws DatabaseException {
895
896        if (secCursor != null && privPKey != null) {
897            return secCursor.getNext(privKey, privPKey, privData, lockMode);
898        } else {
899            return cursor.getNext(privKey, privData, lockMode);
900        }
901    }
902
903    private OperationStatus doGetNextDup(LockMode lockMode)
904        throws DatabaseException {
905
906        if (secCursor != null && privPKey != null) {
907            return secCursor.getNextDup(privKey, privPKey, privData, lockMode);
908        } else {
909            return cursor.getNextDup(privKey, privData, lockMode);
910        }
911    }
912
913    private OperationStatus doGetNextNoDup(LockMode lockMode)
914        throws DatabaseException {
915
916        if (secCursor != null && privPKey != null) {
917            return secCursor.getNextNoDup(privKey, privPKey, privData,
918                                          lockMode);
919        } else {
920            return cursor.getNextNoDup(privKey, privData, lockMode);
921        }
922    }
923
924    private OperationStatus doGetPrev(LockMode lockMode)
925        throws DatabaseException {
926
927        if (secCursor != null && privPKey != null) {
928            return secCursor.getPrev(privKey, privPKey, privData, lockMode);
929        } else {
930            return cursor.getPrev(privKey, privData, lockMode);
931        }
932    }
933
934    private OperationStatus doGetPrevDup(LockMode lockMode)
935        throws DatabaseException {
936
937        if (secCursor != null && privPKey != null) {
938            return secCursor.getPrevDup(privKey, privPKey, privData, lockMode);
939        } else {
940            return cursor.getPrevDup(privKey, privData, lockMode);
941        }
942    }
943
944    private OperationStatus doGetPrevNoDup(LockMode lockMode)
945        throws DatabaseException {
946
947        if (secCursor != null && privPKey != null) {
948            return secCursor.getPrevNoDup(privKey, privPKey, privData,
949                                          lockMode);
950        } else {
951            return cursor.getPrevNoDup(privKey, privData, lockMode);
952        }
953    }
954
955    private OperationStatus doGetSearchKey(LockMode lockMode)
956        throws DatabaseException {
957
958        if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) {
959            return OperationStatus.NOTFOUND;
960        }
961        if (secCursor != null && privPKey != null) {
962            return secCursor.getSearchKey(privKey, privPKey, privData,
963                                          lockMode);
964        } else {
965            return cursor.getSearchKey(privKey, privData, lockMode);
966        }
967    }
968
969    private OperationStatus doGetSearchKeyRange(LockMode lockMode)
970        throws DatabaseException {
971
972        if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) {
973            return OperationStatus.NOTFOUND;
974        }
975        if (secCursor != null && privPKey != null) {
976            return secCursor.getSearchKeyRange(privKey, privPKey, privData,
977                                               lockMode);
978        } else {
979            return cursor.getSearchKeyRange(privKey, privData, lockMode);
980        }
981    }
982
983    private OperationStatus doGetSearchBoth(LockMode lockMode)
984        throws DatabaseException {
985
986        if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) {
987            return OperationStatus.NOTFOUND;
988        }
989        if (secCursor != null && privPKey != null) {
990            return secCursor.getSearchBoth(privKey, privPKey, privData,
991                                           lockMode);
992        } else {
993            return cursor.getSearchBoth(privKey, privData, lockMode);
994        }
995    }
996
997    private OperationStatus doGetSearchBothRange(LockMode lockMode)
998        throws DatabaseException {
999
1000        if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) {
1001            return OperationStatus.NOTFOUND;
1002        }
1003        if (secCursor != null && privPKey != null) {
1004            return secCursor.getSearchBothRange(privKey, privPKey,
1005                                                privData, lockMode);
1006        } else {
1007            return cursor.getSearchBothRange(privKey, privData, lockMode);
1008        }
1009    }
1010
1011    private OperationStatus doGetSearchRecordNumber(LockMode lockMode)
1012        throws DatabaseException {
1013
1014        if (DbCompat.getRecordNumber(privKey) <= 0) {
1015            return OperationStatus.NOTFOUND;
1016        }
1017        if (secCursor != null && privPKey != null) {
1018            return DbCompat.getSearchRecordNumber(secCursor, privKey, privPKey,
1019                                                  privData, lockMode);
1020        } else {
1021            return DbCompat.getSearchRecordNumber(cursor, privKey, privData,
1022                                                  lockMode);
1023        }
1024    }
1025
1026    /*
1027     * Protected methods for duping and closing cursors.  These are overridden
1028     * by the collections API to implement cursor pooling for CDS.
1029     */
1030
1031    /**
1032     * Dups the given cursor.
1033     */
1034    protected Cursor dupCursor(Cursor cursor, boolean samePosition)
1035        throws DatabaseException {
1036
1037        return cursor.dup(samePosition);
1038    }
1039
1040    /**
1041     * Closes the given cursor.
1042     */
1043    protected void closeCursor(Cursor cursor)
1044        throws DatabaseException {
1045
1046        cursor.close();
1047    }
1048
1049    /**
1050     * If the database is a RECNO or QUEUE database, we know its keys are
1051     * record numbers.  We treat a non-positive record number as out of bounds,
1052     * that is, we return NOTFOUND rather than throwing
1053     * IllegalArgumentException as would happen if we passed a non-positive
1054     * record number into the DB cursor.  This behavior is required by the
1055     * collections interface.
1056     */
1057    protected boolean checkRecordNumber() {
1058        return false;
1059    }
1060}
1061