/* * 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.*; /** * Supports two basic operations for moving around a BTree, either forwards or backwards: * moveOne(), and seekTo() * * These two methods, along with movement to the start/end, permit us to construct any desired * movement around a btree, without much cognitive burden. * * This TreeCursor is (and has its methods) package private. This is to avoid polluting the BTreeSearchIterator * that extends it, and uses its functionality. If this class is useful for wider consumption, a public extension * class can be provided, that just makes all of its methods public. */ class TreeCursor<K> extends NodeCursor<K> { // TODO: spend some time optimising compiler inlining decisions: many of these methods have only one primary call-site NodeCursor<K> cur; TreeCursor(Comparator<? super K> comparator, Object[] node) { super(node, null, comparator); } /** * Move the cursor to either the first or last item in the btree * @param start true if should move to the first item; false moves to the last */ void reset(boolean start) { cur = root(); root().inChild = false; // this is a corrupt position, but we ensure we never use it except to start our search from root().position = start ? -1 : getKeyEnd(root().node); } /** * move the Cursor one item, either forwards or backwards * @param forwards direction of travel * @return false iff the cursor is exhausted in the direction of travel */ int moveOne(boolean forwards) { NodeCursor<K> cur = this.cur; if (cur.isLeaf()) { // if we're a leaf, we try to step forwards inside ourselves if (cur.advanceLeafNode(forwards)) return cur.globalLeafIndex(); // if we fail, we just find our bounding parent this.cur = cur = moveOutOfLeaf(forwards, cur, root()); return cur.globalIndex(); } // otherwise we descend directly into our next child if (forwards) ++cur.position; cur = cur.descend(); // and go to its first item NodeCursor<K> next; while ( null != (next = cur.descendToFirstChild(forwards)) ) cur = next; this.cur = cur; return cur.globalLeafIndex(); } /** * seeks from the current position, forwards or backwards, for the provided key * while the direction could be inferred (or ignored), it is required so that (e.g.) we do not infinitely loop on bad inputs * if there is no such key, it moves to the key that would naturally follow/succeed it (i.e. it behaves as ceil when ascending; floor when descending) */ boolean seekTo(K key, boolean forwards, boolean skipOne) { NodeCursor<K> cur = this.cur; /** * decide if we will "try one" value by itself, as a sequential access; * we actually *require* that we try the "current key" for any node before we call seekInNode on it. * * if we are already on a value, we just check it irregardless of if it is a leaf or not; * if we are not, we have already excluded it (as we have consumed it), so: * if we are on a branch we consider that good enough; * otherwise, we move onwards one, and we try the new value * */ boolean tryOne = !skipOne; if ((!tryOne & cur.isLeaf()) && !(tryOne = (cur.advanceLeafNode(forwards) || (cur = moveOutOfLeaf(forwards, cur, null)) != null))) { // we moved out of the tree; return out-of-bounds this.cur = root(); return false; } if (tryOne) { // we're presently on a value we can (and *must*) cheaply test K test = cur.value(); int cmp; if (key == test) cmp = 0; // check object identity first, since we utilise that in some places and it's very cheap else cmp = comparator.compare(test, key); // order of provision matters for asymmetric comparators if (forwards ? cmp >= 0 : cmp <= 0) { // we've either matched, or excluded the value from being present this.cur = cur; return cmp == 0; } } // if we failed to match with the cheap test, first look to see if we're even in the correct sub-tree while (cur != root()) { NodeCursor<K> bound = cur.boundIterator(forwards); if (bound == null) break; // we're all that's left int cmpbound = comparator.compare(bound.bound(forwards), key); // order of provision matters for asymmetric comparators if (forwards ? cmpbound > 0 : cmpbound < 0) break; // already in correct sub-tree // bound is on-or-before target, so ascend to that bound and continue looking upwards cur = bound; cur.safeAdvanceIntoBranchFromChild(forwards); if (cmpbound == 0) // it was an exact match, so terminate here { this.cur = cur; return true; } } // we must now be able to find our target in the sub-tree rooted at cur boolean match; while (!(match = cur.seekInNode(key, forwards)) && !cur.isLeaf()) { cur = cur.descend(); cur.position = forwards ? -1 : getKeyEnd(cur.node); } if (!match) cur = ensureValidLocation(forwards, cur); this.cur = cur; assert !cur.inChild; return match; } /** * ensures a leaf node we have seeked in, is not positioned outside of its bounds, * by moving us into its parents (if any); if it is the root, we're permitted to be out-of-bounds * as this indicates exhaustion */ private NodeCursor<K> ensureValidLocation(boolean forwards, NodeCursor<K> cur) { assert cur.isLeaf(); int position = cur.position; // if we're out of bounds of the leaf, move once in direction of travel if ((position < 0) | (position >= getLeafKeyEnd(cur.node))) cur = moveOutOfLeaf(forwards, cur, root()); return cur; } /** * move out of a leaf node that is currently out of (its own) bounds * @return null if we're now out-of-bounds of the whole tree */ private <K> NodeCursor<K> moveOutOfLeaf(boolean forwards, NodeCursor<K> cur, NodeCursor<K> ifFail) { while (true) { cur = cur.parent; if (cur == null) { root().inChild = false; return ifFail; } if (cur.advanceIntoBranchFromChild(forwards)) break; } cur.inChild = false; return cur; } /** * resets the cursor and seeks to the specified position; does not assume locality or take advantage of the cursor's current position */ void seekTo(int index) { if ((index < 0) | (index >= BTree.size(rootNode()))) { if ((index < -1) | (index > BTree.size(rootNode()))) throw new IndexOutOfBoundsException(index + " not in range [0.." + BTree.size(rootNode()) + ")"); reset(index == -1); return; } NodeCursor<K> cur = root(); assert cur.nodeOffset == 0; while (true) { int relativeIndex = index - cur.nodeOffset; // index within subtree rooted at cur Object[] node = cur.node; if (cur.isLeaf()) { assert relativeIndex < getLeafKeyEnd(node); cur.position = relativeIndex; this.cur = cur; return; } int[] sizeMap = getSizeMap(node); int boundary = Arrays.binarySearch(sizeMap, relativeIndex); if (boundary >= 0) { // exact match, in this branch node assert boundary < sizeMap.length - 1; cur.position = boundary; cur.inChild = false; this.cur = cur; return; } cur.inChild = true; cur.position = -1 -boundary; cur = cur.descend(); } } private NodeCursor<K> root() { return this; } Object[] rootNode() { return this.node; } K currentValue() { return cur.value(); } }