com.sleepycat.collections.BlockIterator Maven / Gradle / Ivy
The newest version!
/*-
* 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 - 2024 Weber Informatics LLC | Privacy Policy