package pt.ist.fenixframework.core.adt.bplustree; import java.util.Iterator; import pt.ist.fenixframework.core.AbstractDomainObject; /** * Inner node of a B+-Tree. These nodes do not contain elements. They only * contain M keys (ordered) and M+1 sub-nodes (M > 0). The n-th sub-node will * contain elements whose keys are all less than the n-th key, except for the * last sub-node (L) which will contain elements whose keys will be greater * than or equal to the M-th key. */ public class InnerNodeArray extends InnerNodeArray_Base { private InnerNodeArray() { super(); } InnerNodeArray(AbstractNodeArray leftNode, AbstractNodeArray rightNode, Comparable splitKey) { setSubNodes(new DoubleArray<AbstractNodeArray>(AbstractNodeArray.class, splitKey, leftNode, rightNode)); leftNode.setParent(this); rightNode.setParent(this); } private InnerNodeArray(DoubleArray<AbstractNodeArray> subNodes) { setSubNodes(subNodes); for (int i = 0; i < subNodes.length(); i++) { // smf: either don't do this or don't setParent when making new subNodes.values[i].setParent(this); } } @Override public AbstractNodeArray insert(Comparable key, AbstractDomainObject value) { return findSubNode(key).insert(key, value); } // this method is invoked when a node in the next depth level got full, it // was split and now needs to pass a new key to its parent (this) AbstractNodeArray rebase(AbstractNodeArray subLeftNode, AbstractNodeArray subRightNode, Comparable middleKey) { DoubleArray<AbstractNodeArray> newArr = justInsertUpdatingParentRelation(middleKey, subLeftNode, subRightNode); if (newArr.length() <= BPlusTreeArray.MAX_NUMBER_OF_ELEMENTS) { // this node can accommodate the new split return getRoot(); } else { // must split this node // find middle position (key to move up amd sub-node to move left) Comparable keyToSplit = newArr.keys[BPlusTreeArray.LOWER_BOUND]; AbstractNodeArray subNodeToMoveLeft = newArr.values[BPlusTreeArray.LOWER_BOUND]; // Split node in two. Notice that the 'keyToSplit' is left out of this level. It will be moved up. DoubleArray<AbstractNodeArray> leftSubNodes = newArr.leftPart(BPlusTreeArray.LOWER_BOUND, 1); leftSubNodes.keys[leftSubNodes.length() - 1] = BPlusTreeArray.LAST_KEY; leftSubNodes.values[leftSubNodes.length() - 1] = subNodeToMoveLeft; InnerNodeArray leftNode = new InnerNodeArray(leftSubNodes); subNodeToMoveLeft.setParent(leftNode); // smf: maybe it is not necessary because of the code in the constructor InnerNodeArray rightNode = new InnerNodeArray(newArr.rightPart(BPlusTreeArray.LOWER_BOUND + 1)); // propagate split to parent if (this.getParent() == null) { InnerNodeArray newRoot = new InnerNodeArray(leftNode, rightNode, keyToSplit); return newRoot; } else { return this.getParent().rebase(leftNode, rightNode, keyToSplit); } } } private DoubleArray<AbstractNodeArray> justInsert(Comparable middleKey, AbstractNodeArray subLeftNode, AbstractNodeArray subRightNode) { DoubleArray<AbstractNodeArray> newArr = getSubNodes().duplicateAndAddKey(middleKey, subLeftNode, subRightNode); setSubNodes(newArr); return newArr; } private DoubleArray<AbstractNodeArray> justInsertUpdatingParentRelation(Comparable middleKey, AbstractNodeArray subLeftNode, AbstractNodeArray subRightNode) { DoubleArray<AbstractNodeArray> newArr = justInsert(middleKey, subLeftNode, subRightNode); subLeftNode.setParent(this); subRightNode.setParent(this); return newArr; } @Override public AbstractNodeArray remove(Comparable key) { return findSubNode(key).remove(key); } AbstractNodeArray replaceDeletedKey(Comparable deletedKey, Comparable replacementKey) { AbstractNodeArray subNode = this.getSubNodes().get(deletedKey); if (subNode != null) { // found the key a this level return replaceDeletedKey(deletedKey, replacementKey, subNode); } else if (this.getParent() != null) { return this.getParent().replaceDeletedKey(deletedKey, replacementKey); } else { return this; } } // replaces the key for the given sub-node. The deletedKey is expected to exist in this node private AbstractNodeArray replaceDeletedKey(Comparable deletedKey, Comparable replacementKey, AbstractNodeArray subNode) { setSubNodes(getSubNodes().replaceKey(deletedKey, replacementKey, subNode)); return getRoot(); } /* * Deal with underflow from LeafNodeArray */ // null in replacement key means that deletedKey does not have to be // replaced. Corollary: the deleted key was not the first key in its leaf // node AbstractNodeArray underflowFromLeaf(Comparable deletedKey, Comparable replacementKey) { DoubleArray<AbstractNodeArray> subNodes = this.getSubNodes(); int iter = 0; // first, identify the deletion point while (BPlusTreeArray.COMPARATOR_SUPPORTING_LAST_KEY.compare(subNodes.keys[iter], deletedKey) <= 0) { iter++; } // Now, 'entryValue' holds the child where the deletion occurred. Comparable entryKey = subNodes.keys[iter]; AbstractNodeArray entryValue = subNodes.values[iter]; Comparable previousEntryKey = iter > 0 ? subNodes.keys[iter - 1] : null; AbstractNodeArray previousEntryValue = iter > 0 ? subNodes.values[iter - 1] : null; Comparable nextEntryKey = null; AbstractNodeArray nextEntryValue = null; /* * Decide whether to shift or merge, and whether to use the left * or the right sibling. We prefer merging to shifting. * * Also, we may need to replace the deleted key in some scenarios * (namely when the key was deleted from the left side of a node * AND that side was not changed by a merge/move with/from the left. */ if (iter == 0) { // the deletedKey was removed from the first sub-node nextEntryKey = subNodes.keys[iter + 1]; // always exists because of LAST_KEY nextEntryValue = subNodes.values[iter + 1]; if (nextEntryValue.shallowSize() == BPlusTreeArray.LOWER_BOUND) { // can we merge with the right? rightLeafMerge(entryKey, entryValue, nextEntryValue); } else { // cannot merge with the right. We have to move an element from the right to here moveChildFromRightToLeft(entryKey, entryValue, nextEntryValue); } if (replacementKey != null && this.getParent() != null) { // the deletedKey occurs somewhere atop only this.getParent().replaceDeletedKey(deletedKey, replacementKey); } } else if (previousEntryValue.shallowSize() == BPlusTreeArray.LOWER_BOUND) { // can we merge with the left? leftLeafMerge(previousEntryKey, previousEntryValue, entryValue); } else { // cannot merge with the left if (iter >= (subNodes.length() - 1) || (nextEntryValue = subNodes.values[iter + 1]).shallowSize() > BPlusTreeArray.LOWER_BOUND) { // caution: tricky test!! // either there is no next or the next is above the lower bound moveChildFromLeftToRight(previousEntryKey, previousEntryValue, entryValue); } else { rightLeafMerge(entryKey, entryValue, nextEntryValue); if (replacementKey != null) { // the deletedKey occurs anywhere (or at this level ONLY?) this.replaceDeletedKey(deletedKey, replacementKey, previousEntryValue); } } } return checkForUnderflow(); } private AbstractNodeArray checkForUnderflow() { DoubleArray<AbstractNodeArray> localSubNodes = this.getSubNodes(); // Now, just check for underflow in this node. The LAST_KEY is fake, so it does not count for the total. if (localSubNodes.length() < BPlusTreeArray.LOWER_BOUND_WITH_LAST_KEY) { // the LAST_KEY is merely an indirection. This only occurs in the root node. We can reduce one depth. if (localSubNodes.length() == 1) { // This only occurs in the root node // (size == 1) => (parent == null), but NOT the inverse assert(this.getParent() == null); AbstractNodeArray child = localSubNodes.firstValue(); child.setParent(null); return child; } else if (this.getParent() != null) { return this.getParent().underflowFromInner(this); } } return getRoot(); } private void rightLeafMerge(Comparable entryKey , AbstractNodeArray entryValue, AbstractNodeArray nextEntryValue) { leftLeafMerge(entryKey, entryValue, nextEntryValue); } private void leftLeafMerge(Comparable previousEntryKey, AbstractNodeArray previousEntryValue, AbstractNodeArray entryValue) { entryValue.mergeWithLeftNode(previousEntryValue, null); // remove the superfluous node setSubNodes(getSubNodes().removeKey(previousEntryKey)); } void mergeWithLeftNode(AbstractNodeArray leftNode, Comparable splitKey) { InnerNodeArray left = (InnerNodeArray)leftNode; // this node does not know how to merge with another kind // change the parent of all the left sub-nodes DoubleArray<AbstractNodeArray> subNodes = this.getSubNodes(); DoubleArray<AbstractNodeArray> leftSubNodes = left.getSubNodes(); InnerNodeArray uncle = subNodes.values[subNodes.length() - 1].getParent(); for (int i = 0; i < leftSubNodes.length(); i++) { leftSubNodes.values[i].setParent(uncle); } DoubleArray<AbstractNodeArray> newArr = subNodes.mergeWith(splitKey, leftSubNodes); setSubNodes(newArr); } // Get the rightmost key-value pair from the left sub-node and move it to the given sub-node. Update the split key private void moveChildFromLeftToRight(Comparable leftEntryKey, AbstractNodeArray leftEntryValue, AbstractNodeArray rightEntryValue) { DoubleArray<AbstractDomainObject>.KeyVal leftBiggestKeyValue = leftEntryValue.removeBiggestKeyValue(); rightEntryValue.addKeyValue(leftBiggestKeyValue); // update the split key to be the key we just moved setSubNodes(this.getSubNodes().replaceKey(leftEntryKey, leftBiggestKeyValue.key, leftEntryValue)); } // Get the leftmost key-value pair from the right sub-node and move it to the given sub-node. Update the split key private void moveChildFromRightToLeft(Comparable leftEntryKey , AbstractNodeArray leftValue, AbstractNodeArray rightValue) { DoubleArray<AbstractDomainObject>.KeyVal rightSmallestKeyValue = rightValue.removeSmallestKeyValue(); leftValue.addKeyValue(rightSmallestKeyValue); // update the split key to be the key after the one we just moved Comparable rightNextSmallestKey = rightValue.getSmallestKey(); DoubleArray<AbstractNodeArray> entries = this.getSubNodes(); setSubNodes(entries.replaceKey(leftEntryKey, rightNextSmallestKey, leftValue)); } /* * Deal with underflow from InnerNodeArray */ AbstractNodeArray underflowFromInner(InnerNodeArray deletedNode) { DoubleArray<AbstractNodeArray> subNodes = this.getSubNodes(); int iter = 0; Comparable entryKey = null; AbstractNodeArray entryValue = null; // first, identify the deletion point do { entryValue = subNodes.values[iter]; iter++; } while (entryValue != deletedNode); // Now, the value() of 'entry' holds the child where the deletion occurred. entryKey = subNodes.keys[iter - 1]; iter--; Comparable previousEntryKey = iter > 0 ? subNodes.keys[iter - 1] : null; AbstractNodeArray previousEntryValue = iter > 0 ? subNodes.values[iter - 1] : null; Comparable nextEntryKey = null; AbstractNodeArray nextEntryValue = null; /* * Decide whether to shift or merge, and whether to use the left * or the right sibling. We prefer merging to shifting. */ if (iter == 0) { // the deletion occurred in the first sub-node nextEntryKey = subNodes.keys[iter + 1]; // always exists because of LAST_KEY nextEntryValue = subNodes.values[iter + 1]; if (nextEntryValue.shallowSize() == BPlusTreeArray.LOWER_BOUND_WITH_LAST_KEY) { // can we merge with the right? rightInnerMerge(entryKey, entryValue, nextEntryValue); } else { // cannot merge with the right. We have to move an element from the right to here rotateRightToLeft(entryKey, (InnerNodeArray)entryValue, (InnerNodeArray)nextEntryValue); } } else if (previousEntryValue.shallowSize() == BPlusTreeArray.LOWER_BOUND_WITH_LAST_KEY) { // can we merge with the left? leftInnerMerge(previousEntryKey, previousEntryValue, entryValue); } else { // cannot merge with the left if (iter >= (subNodes.length() - 1) || (nextEntryValue = subNodes.values[iter + 1]).shallowSize() > BPlusTreeArray.LOWER_BOUND_WITH_LAST_KEY) { // caution: tricky test!! // either there is no next or the next is above the lower bound rotateLeftToRight(previousEntryKey, (InnerNodeArray)previousEntryValue, (InnerNodeArray)entryValue); } else { rightInnerMerge(entryKey, entryValue, nextEntryValue); } } return checkForUnderflow(); } private void rightInnerMerge(Comparable entryKey, AbstractNodeArray entryValue, AbstractNodeArray nextEntryValue) { leftInnerMerge(entryKey, entryValue, nextEntryValue); } private void leftInnerMerge(Comparable previousEntryKey, AbstractNodeArray previousEntryValue, AbstractNodeArray entryValue) { entryValue.mergeWithLeftNode(previousEntryValue, previousEntryKey); // remove the superfluous node setSubNodes(getSubNodes().removeKey(previousEntryKey)); } private void rotateLeftToRight(Comparable leftEntryKey, InnerNodeArray leftSubNode, InnerNodeArray rightSubNode) { DoubleArray<AbstractNodeArray> leftSubNodes = leftSubNode.getSubNodes(); DoubleArray<AbstractNodeArray> rightSubNodes = rightSubNode.getSubNodes(); Comparable leftHighestKey = leftSubNodes.lowerKeyThanHighest(); AbstractNodeArray leftHighestValue = leftSubNodes.lastValue(); // move the highest value from the left to the right. Use the split-key as the index. DoubleArray<AbstractNodeArray> newRightSubNodes = rightSubNodes.addKeyValue(leftEntryKey, leftHighestValue); leftHighestValue.setParent(rightSubNode); // shift a new child to the last entry on the left leftHighestValue = leftSubNodes.get(leftHighestKey); DoubleArray<AbstractNodeArray> newLeftSubNodes = leftSubNodes.removeKey(leftHighestKey); // this is already a duplicated array, no need to go through that process again newLeftSubNodes.values[newLeftSubNodes.length() - 1] = leftHighestValue; leftSubNode.setSubNodes(newLeftSubNodes); rightSubNode.setSubNodes(newRightSubNodes); // update the split-key to be the key we just removed from the left setSubNodes(getSubNodes().replaceKey(leftEntryKey, leftHighestKey, leftSubNode)); } private void rotateRightToLeft(Comparable leftEntryKey, InnerNodeArray leftSubNode, InnerNodeArray rightSubNode) { DoubleArray<AbstractNodeArray> leftSubNodes = leftSubNode.getSubNodes(); DoubleArray<AbstractNodeArray> rightSubNodes = rightSubNode.getSubNodes(); // remove right's lowest entry DoubleArray<AbstractNodeArray>.KeyVal rightLowestEntry = rightSubNodes.getSmallestKeyValue(); DoubleArray<AbstractNodeArray> newRightSubNodes = rightSubNodes.removeSmallestKeyValue(); // re-index the left highest value under the split-key, which is moved down AbstractNodeArray leftHighestValue = leftSubNodes.lastValue(); DoubleArray<AbstractNodeArray> newLeftSubNodes = leftSubNodes.addKeyValue(leftEntryKey, leftHighestValue); // and add the right's lowest entry on the left AbstractNodeArray rightLowestValue = rightLowestEntry.val; // this is already a duplicated array, no need to go through that process again newLeftSubNodes.values[newLeftSubNodes.length() - 1] = rightLowestValue; rightLowestValue.setParent(leftSubNode); leftSubNode.setSubNodes(newLeftSubNodes); rightSubNode.setSubNodes(newRightSubNodes); // update the split-key to be the key we just removed from the right setSubNodes(getSubNodes().replaceKey(leftEntryKey, rightLowestEntry.key, leftSubNode)); } @Override DoubleArray.KeyVal removeBiggestKeyValue() { throw new UnsupportedOperationException("not yet implemented: removeBiggestKeyValue from inner node"); } @Override DoubleArray.KeyVal removeSmallestKeyValue() { throw new UnsupportedOperationException("not yet implemented: removeSmallestKeyValue from inner node"); } @Override Comparable getSmallestKey() { throw new UnsupportedOperationException("not yet implemented: getSmallestKey from inner node"); } @Override void addKeyValue(DoubleArray.KeyVal keyValue) { throw new UnsupportedOperationException("not yet implemented: addKeyValue to inner node should account for LAST_KEY ?!?"); } @Override public AbstractDomainObject get(Comparable key) { return findSubNode(key).get(key); } // travels to the leftmost leaf and goes from there; @Override public AbstractDomainObject getIndex(int index) { return this.getSubNodes().firstValue().getIndex(index); } // travels to the leftmost leaf and goes from there; @Override public AbstractNodeArray removeIndex(int index) { return this.getSubNodes().firstValue().removeIndex(index); } @Override public boolean containsKey(Comparable key) { return findSubNode(key).containsKey(key); } private AbstractNodeArray findSubNode(Comparable key) { DoubleArray<AbstractNodeArray> subNodes = this.getSubNodes(); for (int i = 0; i < subNodes.length(); i++) { Comparable splitKey = subNodes.keys[i]; if (BPlusTreeArray.COMPARATOR_SUPPORTING_LAST_KEY.compare(splitKey, key) > 0) { // this will eventually be true because the LAST_KEY is greater than all return subNodes.values[i]; } } throw new RuntimeException("findSubNode() didn't find a suitable sub-node!?"); } @Override int shallowSize() { return this.getSubNodes().length(); } @Override public int size() { int total = 0; DoubleArray<AbstractNodeArray> subNodes = this.getSubNodes(); for (int i = 0; i < subNodes.length(); i++) { total += subNodes.values[i].size(); } return total; } @Override public Iterator iterator() { return this.getSubNodes().firstValue().iterator(); } @Override public String dump(int level, boolean dumpKeysOnly, boolean dumpNodeIds) { StringBuilder str = new StringBuilder(); StringBuilder spaces = BPlusTreeArray.spaces(level); str.append(spaces); str.append("[" + (dumpNodeIds ? this : "") + ": "); DoubleArray<AbstractNodeArray> subNodes = this.getSubNodes(); for (int i = 0; i < subNodes.length(); i++) { Comparable key = subNodes.keys[i]; AbstractNodeArray value = subNodes.values[i]; str.append("\n"); str.append(value.dump(level + 4, dumpKeysOnly, dumpNodeIds)); str.append(spaces); str.append("(" + key + ") "); } str.append("\n"); str.append(spaces); if (dumpNodeIds) { str.append("] ^" + this.getParent() + "\n"); } else { str.append("]\n"); } return str.toString(); } }