/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.map.diskmap.tree;
import com.github.geophile.erdo.AbstractKey;
import com.github.geophile.erdo.map.LazyRecord;
import com.github.geophile.erdo.map.MapCursor;
import com.github.geophile.erdo.map.MissingKeyAction;
import com.github.geophile.erdo.map.diskmap.DiskPage;
import com.github.geophile.erdo.map.diskmap.IndexRecord;
import com.github.geophile.erdo.util.IdGenerator;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
class TreeLevelCursor extends MapCursor
{
// Object interface
@Override
public String toString()
{
return String.format("TreeLevelCursor(%s)", id);
}
// MapCursor interface
@Override
public LazyRecord next() throws IOException, InterruptedException
{
return neighbor(true);
}
@Override
public LazyRecord previous() throws IOException, InterruptedException
{
return neighbor(false);
}
@Override
public void close()
{
if (state != State.DONE) {
if (position != null) {
position.destroyRecordReference();
position = null;
}
// Don't call end.destroyRecordReference(). end is passed in, and is not owned by this.
super.close();
if (LOG.isLoggable(Level.INFO)) {
LOG.log(Level.INFO, "{0} closed", this);
}
}
}
@Override
protected boolean isOpen(AbstractKey key)
{
throw new UnsupportedOperationException(getClass().getName());
}
// TreeLevelCursor interface
static TreeLevelCursor newCursor(Tree tree, AbstractKey startKey)
{
return new TreeLevelCursor(tree, startKey);
}
// For use by this package
TreeLevelCursor(Tree tree, AbstractKey startKey)
{
super(null, false);
this.tree = tree;
this.startKey = startKey;
LOG.log(Level.INFO, "{0} created", this);
}
// For use by this class
private LazyRecord neighbor(boolean forwardMove) throws IOException, InterruptedException
{
TreePosition neighbor = null;
if (state != State.DONE) {
if (state == State.NEVER_USED || forwardDirection != forwardMove) {
restart(forwardMove ? MissingKeyAction.FORWARD : MissingKeyAction.BACKWARD);
} else {
if (atEnd()) {
close();
} else {
if (forwardMove) {
position.goToNextRecord();
} else {
position.goToPreviousRecord();
}
if (atEnd()) {
close();
}
}
}
}
if (state != State.DONE) {
neighbor = position.copy();
state = State.IN_USE;
forwardDirection = forwardMove;
startKey = neighbor.key();
}
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "{0} {1} to {2}",
new Object[]{this, (forwardMove ? "forward" : "backward"), neighbor});
}
return neighbor;
}
private void restart(MissingKeyAction missingKeyAction) throws IOException, InterruptedException
{
if (startKey == null) {
// Scan an entire Map
// TODO: This does an unnecessary page read if the usage is to create a cursor and then position
// TODO: it as necessary from ForestMapCursor.
position =
missingKeyAction.forward()
? tree.newPosition().level(0).goToFirstSegmentOfLevel().goToFirstPageOfSegment().goToFirstRecordOfPage()
: tree.newPosition().level(0).goToLastSegmentOfLevel().goToLastPageOfSegment().goToLastRecordOfPage();
} else if (missingKeyAction == MissingKeyAction.CLOSE &&
!tree.level(0).keyPossiblyPresent(startKey)) {
assert state == State.NEVER_USED; // implied by MisskingKeyAction.CLOSE
close();
} else {
if (position != null &&
startKey.compareTo(position.page().firstKey()) >= 0 &&
startKey.compareTo(position.page().lastKey()) <= 0) {
// Staying on the same page.
assert position.level().isLeaf();
} else {
// Start cursor at startKey.
position =
// root page
tree.newPosition().level(tree.levels() - 1).goToFirstSegmentOfLevel().goToFirstPageOfSegment();
}
// If we're already on a leaf, then descendToLeaf just sets the position within the leaf.
descendToLeaf(missingKeyAction);
boolean alreadyVisitedStartKey = state == State.IN_USE;
if (alreadyVisitedStartKey && position.key().equals(startKey)) {
if (missingKeyAction.forward()) {
position.goToNextRecord();
} else {
position.goToPreviousRecord();
}
}
}
if (atEnd()) {
close();
}
if (LOG.isLoggable(Level.INFO)) {
LOG.log(Level.INFO, "{0} restarted at {1}: {2}", new Object[]{this, startKey, state});
}
}
private void descendToLeaf(MissingKeyAction missingKeyAction)
throws IOException, InterruptedException
{
int level = position.level().levelNumber();
int recordNumber = recordNumber(position.page(), missingKeyAction);
if (recordNumber == -1) {
assert level == 0 : startKey;
if (missingKeyAction.forward()) {
position.goToNextPage();
if (!position.atEnd()) {
position.goToFirstRecordOfPage();
}
} else {
position.goToPreviousPage();
if (!position.atEnd()) {
position.goToLastRecordOfPage();
}
}
} else {
position.recordNumber(recordNumber);
}
if (level > 0) {
IndexRecord indexRecord = (IndexRecord) position.materializeRecord();
position.level(level - 1).pageAddress(indexRecord.childPageAddress());
descendToLeaf(missingKeyAction);
}
}
// Return -1 to indicate that the resulting cursor needs to be moved off-page. I.e., missingKeyAction == FORWARD
// and startKey > last on page; or missingKeyAction == BACKWARD and startKey < first on page.
private int recordNumber(DiskPage page, MissingKeyAction missingKeyAction)
throws IOException, InterruptedException
{
int recordNumber = page.recordNumber(startKey);
if (recordNumber < 0) {
// startKey not present
if (page.level() == 0) {
// recordNumber is -p-1 where p is insertion point of key.
recordNumber = -recordNumber - 1;
if (recordNumber == page.nRecords() && missingKeyAction == MissingKeyAction.FORWARD ||
recordNumber == 0 && missingKeyAction == MissingKeyAction.BACKWARD) {
recordNumber = -1;
} else if (recordNumber > 0 && missingKeyAction == MissingKeyAction.BACKWARD) {
recordNumber--;
}
} else {
// recordNumber is -p-1 where p is insertion point of key. We are above the leaf level so
// we want the preceding record. if p = 0, then either this is the left most node (page 0),
// or we made a mistake getting here from the parent.
assert page.level() > 0 : startKey;
if (recordNumber == -1) {
int pageNumber = tree.pageNumber(page.pageAddress());
assert pageNumber == 0 : startKey;
recordNumber = 0;
} else {
recordNumber = -recordNumber - 2;
}
}
}
// else: startKey is present
return recordNumber;
}
private boolean atEnd()
{
return position.atEnd();
}
// Class state
private static final Logger LOG = Logger.getLogger(TreeLevelCursor.class.getName());
private static final IdGenerator idGenerator = new IdGenerator(0);
// Object state
private final long id = idGenerator.nextId();
private final Tree tree;
private boolean forwardDirection;
private TreePosition position;
}