
com.sleepycat.collections.BlockIterator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of je Show documentation
Show all versions of je Show documentation
Berkeley DB Java Edition is a open source, transactional storage solution for Java applications. The Direct Persistence Layer (DPL) API is faster and easier to develop, deploy, and manage than serialized object files or ORM-based Java persistence solutions. The Collections API enhances the standard java.util.collections classes allowing them to be persisted to a local file system and accessed concurrently while protected by ACID transactions. Data is stored by serializing objects and managing class and instance data separately so as not to waste space. Berkeley DB Java Edition is the reliable drop-in solution for complex, fast, and scalable storage. Source for this release is in 'je-4.0.92-sources.jar', the Javadoc is located at 'http://download.oracle.com/berkeley-db/docs/je/4.0.92/'.
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.collections;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.util.keyrange.KeyRange;
/**
* An iterator that does not need closing because a cursor is not kept open
* across method calls. A cursor is opened to read a block of records at a
* time and then closed before the method returns.
*
* @author Mark Hayes
*/
class BlockIterator extends BaseIterator {
private StoredCollection coll;
private boolean writeAllowed;
/**
* Slots for a block of record keys and values. The priKeys array is only
* used for secondary databases; otherwise it is set to the keys array.
*/
private byte[][] keys;
private byte[][] priKeys;
private byte[][] values;
/**
* The slot index of the record that would be returned by next().
* nextIndex is always greater or equal to zero. If the next record is not
* available, then nextIndex is equal to keys.length or keys[nextIndex] is
* null.
*
* If the block is empty, then either the iterator is uninitialized or the
* key range is empty. Either way, nextIndex will be the array length and
* all array values will be null. This is the initial state set by the
* constructor. If remove() is used to delete all records in the key
* range, it will restore the iterator to this initial state. The block
* must never be allowed to become empty when the key range is non-empty,
* since then the iterator's position would be lost. [#15858]
*/
private int nextIndex;
/**
* The slot index of the record last returned by next() or previous(), or
* the record inserted by add(). dataIndex is -1 if the data record is not
* available. If greater or equal to zero, the slot at dataIndex is always
* non-null.
*/
private int dataIndex;
/**
* The iterator data last returned by next() or previous(). This value is
* set to null if dataIndex is -1, or if the state of the iterator is such
* that set() or remove() cannot be called. For example, after add() this
* field is set to null, even though the dataIndex is still valid.
*/
private E dataObject;
/**
* Creates an iterator.
*/
BlockIterator(StoredCollection coll,
boolean writeAllowed,
int blockSize) {
this.coll = coll;
this.writeAllowed = writeAllowed;
keys = new byte[blockSize][];
priKeys = coll.isSecondary() ? (new byte[blockSize][]) : keys;
values = new byte[blockSize][];
nextIndex = blockSize;
dataIndex = -1;
dataObject = null;
}
/**
* Copy constructor.
*/
private BlockIterator(BlockIterator o) {
coll = o.coll;
writeAllowed = o.writeAllowed;
keys = copyArray(o.keys);
priKeys = coll.isSecondary() ? copyArray(o.priKeys) : keys;
values = copyArray(o.values);
nextIndex = o.nextIndex;
dataIndex = o.dataIndex;
dataObject = o.dataObject;
}
/**
* Copies an array of byte arrays.
*/
private byte[][] copyArray(byte[][] a) {
byte[][] b = new byte[a.length][];
for (int i = 0; i < b.length; i += 1) {
if (a[i] != null) {
b[i] = KeyRange.copyBytes(a[i]);
}
}
return b;
}
/**
* Returns whether the element at nextIndex is available.
*/
private boolean isNextAvailable() {
return (nextIndex < keys.length) &&
(keys[nextIndex] != null);
}
/**
* Returns whether the element at nextIndex-1 is available.
*/
private boolean isPrevAvailable() {
return (nextIndex > 0) &&
(keys[nextIndex - 1] != null);
}
/**
* Returns the record number at the given slot position.
*/
private int getRecordNumber(int i) {
if (coll.view.btreeRecNumDb) {
DataCursor cursor = null;
try {
cursor = new DataCursor(coll.view, false);
if (!moveCursor(i, cursor)) {
throw new IllegalStateException();
}
return cursor.getCurrentRecordNumber();
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
coll.closeCursor(cursor);
}
} else {
DatabaseEntry entry = new DatabaseEntry(keys[i]);
return DbCompat.getRecordNumber(entry);
}
}
/**
* Sets dataObject to the iterator data for the element at dataIndex.
*/
private void makeDataObject() {
int i = dataIndex;
DatabaseEntry keyEntry = new DatabaseEntry(keys[i]);
DatabaseEntry priKeyEntry = (keys != priKeys)
? (new DatabaseEntry(priKeys[i]))
: keyEntry;
DatabaseEntry valuesEntry = new DatabaseEntry(values[i]);
dataObject = coll.makeIteratorData(this, keyEntry, priKeyEntry,
valuesEntry);
}
/**
* Sets all slots to null.
*/
private void clearSlots() {
for (int i = 0; i < keys.length; i += 1) {
keys[i] = null;
priKeys[i] = null;
values[i] = null;
}
}
/**
* Sets a given slot using the data in the given cursor.
*/
private void setSlot(int i, DataCursor cursor) {
keys[i] = KeyRange.getByteArray(cursor.getKeyThang());
if (keys != priKeys) {
priKeys[i] = KeyRange.getByteArray
(cursor.getPrimaryKeyThang());
}
values[i] = KeyRange.getByteArray(cursor.getValueThang());
}
/**
* Inserts an added record at a given slot position and shifts other slots
* accordingly. Also adjusts nextIndex and sets dataIndex to -1.
*/
private void insertSlot(int i, DataCursor cursor) {
if (i < keys.length) {
for (int j = keys.length - 1; j > i; j -= 1) {
/* Shift right. */
keys[j] = keys[j - 1];
priKeys[j] = priKeys[j - 1];
values[j] = values[j - 1];
/* Bump key in recno-renumber database. */
if (coll.view.recNumRenumber && keys[j] != null) {
bumpRecordNumber(j);
}
}
nextIndex += 1;
} else {
if (i != keys.length) {
throw DbCompat.unexpectedState();
}
i -= 1;
for (int j = 0; j < i; j += 1) {
/* Shift left. */
keys[j] = keys[j + 1];
priKeys[j] = priKeys[j + 1];
values[j] = values[j + 1];
}
}
setSlot(i, cursor);
dataIndex = -1;
}
/**
* Increments the record number key at the given slot.
*/
private void bumpRecordNumber(int i) {
DatabaseEntry entry = new DatabaseEntry(keys[i]);
DbCompat.setRecordNumber(entry,
DbCompat.getRecordNumber(entry) + 1);
keys[i] = entry.getData();
}
/**
* Deletes the given slot, adjusts nextIndex and sets dataIndex to -1.
*/
private void deleteSlot(int i) {
for (int j = i + 1; j < keys.length; j += 1) {
keys[j - 1] = keys[j];
priKeys[j - 1] = priKeys[j];
values[j - 1] = values[j];
}
int last = keys.length - 1;
keys[last] = null;
priKeys[last] = null;
values[last] = null;
if (nextIndex > i) {
nextIndex -= 1;
}
dataIndex = -1;
}
/**
* Moves the cursor to the key/data at the given slot, and returns false
* if the reposition (search) fails.
*/
private boolean moveCursor(int i, DataCursor cursor)
throws DatabaseException {
return cursor.repositionExact(keys[i], priKeys[i], values[i], false);
}
// --- begin Iterator/ListIterator methods ---
public boolean hasNext() {
if (isNextAvailable()) {
return true;
}
DataCursor cursor = null;
try {
cursor = new DataCursor(coll.view, writeAllowed);
int prev = nextIndex - 1;
boolean found = false;
if (keys[prev] == null) {
/* Get the first record for an uninitialized iterator. */
OperationStatus status = cursor.getFirst(false);
if (status == OperationStatus.SUCCESS) {
found = true;
nextIndex = 0;
}
} else {
/* Reposition to the last known key/data pair. */
int repos = cursor.repositionRange
(keys[prev], priKeys[prev], values[prev], false);
if (repos == DataCursor.REPOS_EXACT) {
/*
* The last known key/data pair was found and will now be
* in slot zero.
*/
found = true;
nextIndex = 1;
/* The data object is now in slot zero or not available. */
if (dataIndex == prev) {
dataIndex = 0;
} else {
dataIndex = -1;
dataObject = null;
}
} else if (repos == DataCursor.REPOS_NEXT) {
/*
* The last known key/data pair was not found, but the
* following record was found and it will be in slot zero.
*/
found = true;
nextIndex = 0;
/* The data object is no longer available. */
dataIndex = -1;
dataObject = null;
} else {
if (repos != DataCursor.REPOS_EOF) {
throw DbCompat.unexpectedState();
}
}
}
if (found) {
/* Clear all slots first in case an exception occurs below. */
clearSlots();
/* Attempt to fill all slots with records. */
int i = 0;
boolean done = false;
while (!done) {
setSlot(i, cursor);
i += 1;
if (i < keys.length) {
OperationStatus status = coll.iterateDuplicates() ?
cursor.getNext(false) :
cursor.getNextNoDup(false);
if (status != OperationStatus.SUCCESS) {
done = true;
}
} else {
done = true;
}
}
}
/*
* If REPOS_EXACT was returned above, make sure we retrieved
* the following record.
*/
return isNextAvailable();
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
coll.closeCursor(cursor);
}
}
public boolean hasPrevious() {
if (isPrevAvailable()) {
return true;
}
if (!isNextAvailable()) {
return false;
}
DataCursor cursor = null;
try {
cursor = new DataCursor(coll.view, writeAllowed);
int last = keys.length - 1;
int next = nextIndex;
boolean found = false;
/* Reposition to the last known key/data pair. */
int repos = cursor.repositionRange
(keys[next], priKeys[next], values[next], false);
if (repos == DataCursor.REPOS_EXACT ||
repos == DataCursor.REPOS_NEXT) {
/*
* The last known key/data pair, or the record following it,
* was found and will now be in the last slot.
*/
found = true;
nextIndex = last;
/* The data object is now in the last slot or not available. */
if (dataIndex == next && repos == DataCursor.REPOS_EXACT) {
dataIndex = last;
} else {
dataIndex = -1;
dataObject = null;
}
} else {
if (repos != DataCursor.REPOS_EOF) {
throw DbCompat.unexpectedState();
}
}
if (found) {
/* Clear all slots first in case an exception occurs below. */
clearSlots();
/* Attempt to fill all slots with records. */
int i = last;
boolean done = false;
while (!done) {
setSlot(i, cursor);
i -= 1;
if (i >= 0) {
OperationStatus status = coll.iterateDuplicates() ?
cursor.getPrev(false) :
cursor.getPrevNoDup(false);
if (status != OperationStatus.SUCCESS) {
done = true;
}
} else {
done = true;
}
}
}
/*
* Make sure we retrieved the preceding record after the reposition
* above.
*/
return isPrevAvailable();
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
coll.closeCursor(cursor);
}
}
public E next() {
if (hasNext()) {
dataIndex = nextIndex;
nextIndex += 1;
makeDataObject();
return dataObject;
} else {
throw new NoSuchElementException();
}
}
public E previous() {
if (hasPrevious()) {
nextIndex -= 1;
dataIndex = nextIndex;
makeDataObject();
return dataObject;
} else {
throw new NoSuchElementException();
}
}
public int nextIndex() {
if (!coll.view.recNumAccess) {
throw new UnsupportedOperationException
("Record number access not supported");
}
return hasNext() ? (getRecordNumber(nextIndex) -
coll.getIndexOffset())
: Integer.MAX_VALUE;
}
public int previousIndex() {
if (!coll.view.recNumAccess) {
throw new UnsupportedOperationException
("Record number access not supported");
}
return hasPrevious() ? (getRecordNumber(nextIndex - 1) -
coll.getIndexOffset())
: (-1);
}
public void set(E value) {
if (dataObject == null) {
throw new IllegalStateException();
}
if (!coll.hasValues()) {
throw new UnsupportedOperationException();
}
DataCursor cursor = null;
boolean doAutoCommit = coll.beginAutoCommit();
try {
cursor = new DataCursor(coll.view, writeAllowed);
if (moveCursor(dataIndex, cursor)) {
cursor.putCurrent(value);
setSlot(dataIndex, cursor);
coll.closeCursor(cursor);
coll.commitAutoCommit(doAutoCommit);
} else {
throw new IllegalStateException();
}
} catch (Exception e) {
coll.closeCursor(cursor);
throw coll.handleException(e, doAutoCommit);
}
}
public void remove() {
if (dataObject == null) {
throw new IllegalStateException();
}
DataCursor cursor = null;
boolean doAutoCommit = coll.beginAutoCommit();
try {
cursor = new DataCursor(coll.view, writeAllowed);
if (moveCursor(dataIndex, cursor)) {
cursor.delete();
deleteSlot(dataIndex);
dataObject = null;
/*
* Repopulate the block after removing all records, using the
* cursor position of the deleted record as a starting point.
* First try moving forward, since the user was moving forward.
* (It is possible to delete all records in the block only by
* moving forward, i.e, when nextIndex is greater than
* dataIndex.)
*/
if (nextIndex == 0 && keys[0] == null) {
OperationStatus status;
for (int i = 0; i < keys.length; i += 1) {
status = coll.iterateDuplicates() ?
cursor.getNext(false) :
cursor.getNextNoDup(false);
if (status == OperationStatus.SUCCESS) {
setSlot(i, cursor);
} else {
break;
}
}
/*
* If no records are found past the cursor position, try
* moving backward. If no records are found before the
* cursor position, leave nextIndex set to keys.length,
* which is the same as the initial iterator state and is
* appropriate for an empty key range.
*/
if (keys[0] == null) {
nextIndex = keys.length;
for (int i = nextIndex - 1; i >= 0; i -= 1) {
status = coll.iterateDuplicates() ?
cursor.getPrev(false) :
cursor.getPrevNoDup(false);
if (status == OperationStatus.SUCCESS) {
setSlot(i, cursor);
} else {
break;
}
}
}
}
coll.closeCursor(cursor);
coll.commitAutoCommit(doAutoCommit);
} else {
throw new IllegalStateException();
}
} catch (Exception e) {
coll.closeCursor(cursor);
throw coll.handleException(e, doAutoCommit);
}
}
public void add(E value) {
/*
* The checkIterAddAllowed method ensures that one of the following two
* conditions holds and throws UnsupportedOperationException otherwise:
* 1- This is a list iterator for a recno-renumber database.
* 2- This is a collection iterator for a duplicates database.
*/
coll.checkIterAddAllowed();
OperationStatus status = OperationStatus.SUCCESS;
DataCursor cursor = null;
boolean doAutoCommit = coll.beginAutoCommit();
try {
if (coll.view.keysRenumbered || !coll.areDuplicatesOrdered()) {
/*
* This is a recno-renumber database or a btree/hash database
* with unordered duplicates.
*/
boolean hasPrev = hasPrevious();
if (!hasPrev && !hasNext()) {
/* The collection is empty. */
if (coll.view.keysRenumbered) {
/* Append to an empty recno-renumber database. */
status = coll.view.append(value, null, null);
} else if (coll.view.dupsAllowed &&
coll.view.range.isSingleKey()) {
/*
* When inserting a duplicate into a single-key range,
* the main key is fixed, so we can always insert into
* an empty collection.
*/
cursor = new DataCursor(coll.view, writeAllowed);
cursor.useRangeKey();
status = cursor.putNoDupData(null, value, null, true);
coll.closeCursor(cursor);
cursor = null;
} else {
throw new IllegalStateException
("Collection is empty, cannot add() duplicate");
}
/*
* Move past the record just inserted (the cursor should be
* closed above to prevent multiple open cursors in certain
* DB core modes).
*/
if (status == OperationStatus.SUCCESS) {
next();
dataIndex = nextIndex - 1;
}
} else {
/*
* The collection is non-empty. If hasPrev is true then
* the element at (nextIndex - 1) is available; otherwise
* the element at nextIndex is available.
*/
cursor = new DataCursor(coll.view, writeAllowed);
int insertIndex = hasPrev ? (nextIndex - 1) : nextIndex;
if (!moveCursor(insertIndex, cursor)) {
throw new IllegalStateException();
}
/*
* For a recno-renumber database or a database with
* unsorted duplicates, insert before the iterator 'next'
* position, or after the 'prev' position. Then adjust
* the slots to account for the inserted record.
*/
status = hasPrev ? cursor.putAfter(value)
: cursor.putBefore(value);
if (status == OperationStatus.SUCCESS) {
insertSlot(nextIndex, cursor);
}
}
} else {
/* This is a btree/hash database with ordered duplicates. */
cursor = new DataCursor(coll.view, writeAllowed);
if (coll.view.range.isSingleKey()) {
/*
* When inserting a duplicate into a single-key range,
* the main key is fixed.
*/
cursor.useRangeKey();
} else {
/*
* When inserting into a multi-key range, the main key
* is the last dataIndex accessed by next(), previous()
* or add().
*/
if (dataIndex < 0 || !moveCursor(dataIndex, cursor)) {
throw new IllegalStateException();
}
}
/*
* For a hash/btree with duplicates, insert the duplicate,
* put the new record in slot zero, and set the next index
* to slot one (past the new record).
*/
status = cursor.putNoDupData(null, value, null, true);
if (status == OperationStatus.SUCCESS) {
clearSlots();
setSlot(0, cursor);
dataIndex = 0;
nextIndex = 1;
}
}
if (status == OperationStatus.KEYEXIST) {
throw new IllegalArgumentException("Duplicate value");
} else if (status != OperationStatus.SUCCESS) {
DbCompat.unexpectedState("Could not insert: " + status);
}
/* Prevent subsequent set() or remove() call. */
dataObject = null;
coll.closeCursor(cursor);
coll.commitAutoCommit(doAutoCommit);
} catch (Exception e) {
/* Catch RuntimeExceptions too. */
coll.closeCursor(cursor);
throw coll.handleException(e, doAutoCommit);
}
}
// --- end Iterator/ListIterator methods ---
// --- begin BaseIterator methods ---
final ListIterator dup() {
return new BlockIterator(this);
}
final boolean isCurrentData(Object currentData) {
return (dataObject == currentData);
}
final boolean moveToIndex(int index) {
DataCursor cursor = null;
try {
cursor = new DataCursor(coll.view, writeAllowed);
OperationStatus status =
cursor.getSearchKey(Integer.valueOf(index), null, false);
boolean retVal;
if (status == OperationStatus.SUCCESS) {
clearSlots();
setSlot(0, cursor);
nextIndex = 0;
retVal = true;
} else {
retVal = false;
}
return retVal;
} catch (Exception e) {
throw StoredContainer.convertException(e);
} finally {
coll.closeCursor(cursor);
}
}
// --- end BaseIterator methods ---
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy