blob: e9fa89e9317f2838515682e9b3b790eac601c43a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cassandra.utils.btree;
import java.util.Arrays;
import java.util.Comparator;
import static org.apache.cassandra.utils.btree.BTree.*;
/**
* A class for searching within one node of a btree: a linear chain (stack) of these is built of tree height
* to form a Cursor. Some corollaries of the basic building block operations in TreeCursor (moveOne and seekTo),
* along with some other methods for helping implement movement between two NodeCursor
*
* The behaviour is not dissimilar to that of NodeBuilder and TreeBuilder, wherein functions that may move
* us to a different node pass us the node we should move to, from which we continue our operations.
* @param <K>
*/
class NodeCursor<K>
{
// TODO: consider splitting forwards from backwards
final NodeCursor<K> parent, child;
final Comparator<? super K> comparator;
boolean inChild;
// if !inChild, this is the key position we are currently on;
// if inChild, this is the child position we are currently descending into
int position;
Object[] node;
int nodeOffset;
NodeCursor(Object[] node, NodeCursor<K> parent, Comparator<? super K> comparator)
{
this.node = node;
this.parent = parent;
this.comparator = comparator;
// a well formed b-tree (text book, or ours) must be balanced, so by building a stack following the left-most branch
// we have a stack capable of visiting any path in the tree
this.child = BTree.isLeaf(node) ? null : new NodeCursor<>((Object[]) node[getChildStart(node)], this, comparator);
}
void resetNode(Object[] node, int nodeOffset)
{
this.node = node;
this.nodeOffset = nodeOffset;
}
/**
* adapt child position to key position within branch, knowing it is safe to do so
*/
void safeAdvanceIntoBranchFromChild(boolean forwards)
{
if (!forwards)
--position;
}
/**
* adapt child position to key position within branch, and return if this was successful or we're now out of bounds
*/
boolean advanceIntoBranchFromChild(boolean forwards)
{
return forwards ? position < getBranchKeyEnd(node) : --position >= 0;
}
boolean advanceLeafNode(boolean forwards)
{
return forwards ? ++position < getLeafKeyEnd(node)
: --position >= 0;
}
/**
* @return the upper/lower bound of the child we are currently descended in
*/
K bound(boolean upper)
{
return (K) node[position - (upper ? 0 : 1)];
}
/**
* The parent that covers a range wider than ourselves, either ascending or descending,
* i.e. that defines the upper or lower bound on the subtree rooted at our node
* @param upper
* @return the NodeCursor parent that can tell us the upper/lower bound of ourselves
*/
NodeCursor<K> boundIterator(boolean upper)
{
NodeCursor<K> bound = this.parent;
while (bound != null && (upper ? bound.position >= getChildCount(bound.node) - 1
: bound.position <= 0))
bound = bound.parent;
return bound;
}
/**
* look for the provided key in this node, in the specified direction:
* forwards => ceil search; otherwise floor
*
* we require that the node's "current" key (including the relevant bound if we are a parent we have ascended into)
* be already excluded by the search. this is useful for the following reasons:
* 1: we must ensure we never go backwards, so excluding that key from our binary search prevents our
* descending into a child we have already visited (without any further checks)
* 2: we already check the bounds as we search upwards for our natural parent;
* 3: we want to cheaply check sequential access, so we always check the first key we're on anyway (if it can be done easily)
*/
boolean seekInNode(K key, boolean forwards)
{
int position = this.position;
int lb, ub;
if (forwards)
{
lb = position + 1;
ub = getKeyEnd(node);
}
else
{
ub = position;
lb = 0;
}
int find = Arrays.binarySearch((K[]) node, lb, ub, key, comparator);
if (find >= 0)
{
// exact key match, so we're in the correct node already. return success
this.position = find;
inChild = false;
return true;
}
// if we are a branch, and we are an inequality match, the direction of travel doesn't matter
// so we only need to modify if we are going backwards on a leaf node, to produce floor semantics
int delta = isLeaf() & !forwards ? -1 : 0;
this.position = delta -1 -find;
return false;
}
NodeCursor<K> descendToFirstChild(boolean forwards)
{
if (isLeaf())
{
position = forwards ? 0 : getLeafKeyEnd(node) - 1;
return null;
}
inChild = true;
position = forwards ? 0 : getChildCount(node) - 1;
return descend();
}
// descend into the child at "position"
NodeCursor<K> descend()
{
Object[] childNode = (Object[]) node[position + getChildStart(node)];
int childOffset = nodeOffset + treeIndexOffsetOfChild(node, position);
child.resetNode(childNode, childOffset);
inChild = true;
return child;
}
boolean isLeaf()
{
return child == null;
}
int globalIndex()
{
return nodeOffset + treeIndexOfKey(node, position);
}
int globalLeafIndex()
{
return nodeOffset + treeIndexOfLeafKey(position);
}
int globalBranchIndex()
{
return nodeOffset + treeIndexOfBranchKey(node, position);
}
K value()
{
return (K) node[position];
}
}