package org.apache.cassandra.utils.btree;
import java.util.Arrays;
import java.util.Comparator;
import static org.apache.cassandra.utils.btree.BTree.EMPTY_BRANCH;
import static org.apache.cassandra.utils.btree.BTree.FAN_FACTOR;
import static org.apache.cassandra.utils.btree.BTree.POSITIVE_INFINITY;
import static org.apache.cassandra.utils.btree.BTree.compare;
import static org.apache.cassandra.utils.btree.BTree.find;
import static org.apache.cassandra.utils.btree.BTree.getKeyEnd;
import static org.apache.cassandra.utils.btree.BTree.isLeaf;
/**
* Represents a level / stack item of in progress modifications to a BTree.
*/
final class NodeBuilder
{
private static final int MAX_KEYS = 1 + (FAN_FACTOR * 2);
// parent stack
private NodeBuilder parent, child;
// buffer for building new nodes
private Object[] buildKeys = new Object[MAX_KEYS]; // buffers keys for branches and leaves
private Object[] buildChildren = new Object[1 + MAX_KEYS]; // buffers children for branches only
private int buildKeyPosition;
private int buildChildPosition;
// we null out the contents of buildKeys/buildChildren when clear()ing them for re-use; this is where
// we track how much we actually have to null out
private int maxBuildKeyPosition;
private int maxBuildChildPosition;
// current node of the btree we're modifying/copying from
private Object[] copyFrom;
// the index of the first key in copyFrom that has not yet been copied into the build arrays
private int copyFromKeyPosition;
// the index of the first child node in copyFrom that has not yet been copied into the build arrays
private int copyFromChildPosition;
// upper bound of range owned by this level; lets us know if we need to ascend back up the tree
// for the next key we update when bsearch gives an insertion point past the end of the values
// in the current node
private Object upperBound;
// ensure we aren't referencing any garbage
void clear()
{
NodeBuilder current = this;
while (current != null)
{
if (current.upperBound != null)
{
current.reset(null, null);
Arrays.fill(current.buildKeys, 0, current.maxBuildKeyPosition, null);
Arrays.fill(current.buildChildren, 0, current.maxBuildChildPosition, null);
current.maxBuildChildPosition = current.maxBuildKeyPosition = 0;
}
current = current.child;
}
}
// reset counters/setup to copy from provided node
void reset(Object[] copyFrom, Object upperBound)
{
this.copyFrom = copyFrom;
this.upperBound = upperBound;
maxBuildKeyPosition = Math.max(maxBuildKeyPosition, buildKeyPosition);
maxBuildChildPosition = Math.max(maxBuildChildPosition, buildChildPosition);
buildKeyPosition = 0;
buildChildPosition = 0;
copyFromKeyPosition = 0;
copyFromChildPosition = 0;
}
/**
* Inserts or replaces the provided key, copying all not-yet-visited keys prior to it into our buffer.
*
* @param key key we are inserting/replacing
* @return the NodeBuilder to retry the update against (a child if we own the range being updated,
* a parent if we do not -- we got here from an earlier key -- and we need to ascend back up),
* or null if we finished the update in this node.
*/
<V> NodeBuilder update(Object key, Comparator<V> comparator, ReplaceFunction<V> replaceF)
{
assert copyFrom != null;
int copyFromKeyEnd = getKeyEnd(copyFrom);
int i = find(comparator, (V) key, copyFrom, copyFromKeyPosition, copyFromKeyEnd);
boolean found = i >= 0; // exact key match?
boolean owns = true; // true iff this node (or a child) should contain the key
if (!found)
{
i = -i - 1;
if (i == copyFromKeyEnd && compare(comparator, upperBound, key) <= 0)
owns = false;
}
if (isLeaf(copyFrom))
{
// copy keys from the original node up to prior to the found index
copyKeys(i);
if (owns)
{
if (found)
replaceNextKey(key, replaceF);
else
addNewKey(key, replaceF); // handles splitting parent if necessary via ensureRoom
// done, so return null
return null;
}
// if we don't own it, all we need to do is ensure we've copied everything in this node
// (which we have done, since not owning means pos >= keyEnd), ascend, and let Modifier.update
// retry against the parent node. The if/ascend after the else branch takes care of that.
}
else
{
// branch
if (found)
{
copyKeys(i);
replaceNextKey(key, replaceF);
copyChildren(i + 1);
return null;
}
else if (owns)
{
copyKeys(i);
copyChildren(i);
// belongs to the range owned by this node, but not equal to any key in the node
// so descend into the owning child
Object newUpperBound = i < copyFromKeyEnd ? copyFrom[i] : upperBound;
Object[] descendInto = (Object[]) copyFrom[copyFromKeyEnd + i];
ensureChild().reset(descendInto, newUpperBound);
return child;
}
else
{
// ensure we've copied all keys and children
copyKeys(copyFromKeyEnd);
copyChildren(copyFromKeyEnd + 1); // since we know that there are exactly 1 more child nodes, than keys
}
}
if (key == POSITIVE_INFINITY && isRoot())
return null;
return ascend();
}
// UTILITY METHODS FOR IMPLEMENTATION OF UPDATE/BUILD/DELETE
boolean isRoot()
{
// if parent == null, or parent.upperBound == null, then we have not initialised a parent builder,
// so we are the top level builder holding modifications; if we have more than FAN_FACTOR items, though,
// we are not a valid root so we would need to spill-up to create a new root
return (parent == null || parent.upperBound == null) && buildKeyPosition <= FAN_FACTOR;
}
// ascend to the root node, splitting into proper node sizes as we go; useful for building
// where we work only on the newest child node, which may construct many spill-over parents as it goes
NodeBuilder ascendToRoot()
{
NodeBuilder current = this;
while (!current.isRoot())
current = current.ascend();
return current;
}
// builds a new root BTree node - must be called on root of operation
Object[] toNode()
{
assert buildKeyPosition <= FAN_FACTOR && buildKeyPosition > 0 : buildKeyPosition;
return buildFromRange(0, buildKeyPosition, isLeaf(copyFrom));
}
// finish up this level and pass any constructed children up to our parent, ensuring a parent exists
private NodeBuilder ascend()
{
ensureParent();
boolean isLeaf = isLeaf(copyFrom);
if (buildKeyPosition > FAN_FACTOR)
{
// split current node and move the midpoint into parent, with the two halves as children
int mid = buildKeyPosition / 2;
parent.addExtraChild(buildFromRange(0, mid, isLeaf), buildKeys[mid]);
parent.finishChild(buildFromRange(mid + 1, buildKeyPosition - (mid + 1), isLeaf));
}
else
{
parent.finishChild(buildFromRange(0, buildKeyPosition, isLeaf));
}
return parent;
}
// copy keys from copyf to the builder, up to the provided index in copyf (exclusive)
private void copyKeys(int upToKeyPosition)
{
if (copyFromKeyPosition >= upToKeyPosition)
return;
int len = upToKeyPosition - copyFromKeyPosition;
assert len <= FAN_FACTOR : upToKeyPosition + "," + copyFromKeyPosition;
ensureRoom(buildKeyPosition + len);
System.arraycopy(copyFrom, copyFromKeyPosition, buildKeys, buildKeyPosition, len);
copyFromKeyPosition = upToKeyPosition;
buildKeyPosition += len;
}
// skips the next key in copyf, and puts the provided key in the builder instead
private <V> void replaceNextKey(Object with, ReplaceFunction<V> replaceF)
{
// (this first part differs from addNewKey in that we pass the replaced object to replaceF as well)
ensureRoom(buildKeyPosition + 1);
if (replaceF != null)
with = replaceF.apply((V) copyFrom[copyFromKeyPosition], (V) with);
buildKeys[buildKeyPosition++] = with;
copyFromKeyPosition++;
}
// puts the provided key in the builder, with no impact on treatment of data from copyf
<V> void addNewKey(Object key, ReplaceFunction<V> replaceF)
{
ensureRoom(buildKeyPosition + 1);
if (replaceF != null)
key = replaceF.apply((V) key);
buildKeys[buildKeyPosition++] = key;
}
// copies children from copyf to the builder, up to the provided index in copyf (exclusive)
private void copyChildren(int upToChildPosition)
{
// (ensureRoom isn't called here, as we should always be at/behind key additions)
if (copyFromChildPosition >= upToChildPosition)
return;
int len = upToChildPosition - copyFromChildPosition;
System.arraycopy(copyFrom, getKeyEnd(copyFrom) + copyFromChildPosition, buildChildren, buildChildPosition, len);
copyFromChildPosition = upToChildPosition;
buildChildPosition += len;
}
// adds a new and unexpected child to the builder - called by children that overflow
private void addExtraChild(Object[] child, Object upperBound)
{
ensureRoom(buildKeyPosition + 1);
buildKeys[buildKeyPosition++] = upperBound;
buildChildren[buildChildPosition++] = child;
}
// adds a replacement expected child to the builder - called by children prior to ascending
private void finishChild(Object[] child)
{
buildChildren[buildChildPosition++] = child;
copyFromChildPosition++;
}
// checks if we can add the requested keys+children to the builder, and if not we spill-over into our parent
private void ensureRoom(int nextBuildKeyPosition)
{
if (nextBuildKeyPosition < MAX_KEYS)
return;
// flush even number of items so we don't waste leaf space repeatedly
Object[] flushUp = buildFromRange(0, FAN_FACTOR, isLeaf(copyFrom));
ensureParent().addExtraChild(flushUp, buildKeys[FAN_FACTOR]);
int size = FAN_FACTOR + 1;
assert size <= buildKeyPosition : buildKeyPosition + "," + nextBuildKeyPosition;
System.arraycopy(buildKeys, size, buildKeys, 0, buildKeyPosition - size);
buildKeyPosition -= size;
maxBuildKeyPosition = buildKeys.length;
if (buildChildPosition > 0)
{
System.arraycopy(buildChildren, size, buildChildren, 0, buildChildPosition - size);
buildChildPosition -= size;
maxBuildChildPosition = buildChildren.length;
}
}
// builds and returns a node from the buffered objects in the given range
private Object[] buildFromRange(int offset, int keyLength, boolean isLeaf)
{
Object[] a;
if (isLeaf)
{
a = new Object[keyLength + (keyLength & 1)];
System.arraycopy(buildKeys, offset, a, 0, keyLength);
}
else
{
a = new Object[1 + (keyLength * 2)];
System.arraycopy(buildKeys, offset, a, 0, keyLength);
System.arraycopy(buildChildren, offset, a, keyLength, keyLength + 1);
}
return a;
}
// checks if there is an initialised parent, and if not creates/initialises one and returns it.
// different to ensureChild, as we initialise here instead of caller, as parents in general should
// already be initialised, and only aren't in the case where we are overflowing the original root node
private NodeBuilder ensureParent()
{
if (parent == null)
{
parent = new NodeBuilder();
parent.child = this;
}
if (parent.upperBound == null)
parent.reset(EMPTY_BRANCH, upperBound);
return parent;
}
// ensures a child level exists and returns it
NodeBuilder ensureChild()
{
if (child == null)
{
child = new NodeBuilder();
child.parent = this;
}
return child;
}
}