/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.store.xa;
// Java 2 standard packages
import java.io.*;
// Third party packages
import org.apache.log4j.Logger;
// Local packages
import org.mulgara.util.Constants;
/**
* These are the nodes which make up an AVL tree.
* Each node contains up to two children, with the difference between the maximum
* depths of the left and right children being no more than one. This difference
* is called the balance.
*
* @created 2002-10-04
*
* @author David Makepeace
* @author Paula Gearon
*
* @version $Revision: 1.10 $
*
* @modified $Date: 2005/07/05 04:23:54 $
*
* @maintenanceAuthor $Author: pgearon $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright ©2001 <a href="http://www.pisoftware.com/">Plugged In
* Software Pty Ltd</a>
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public final class AVLNode {
/** The index in longs within the block of the left child node. */
private final static int IDX_LEFT = 0;
/** The index in longs within the block of the right child node. */
private final static int IDX_RIGHT = IDX_LEFT + 1;
/** The size in longs of the header data for the node. */
public final static int HEADER_SIZE = IDX_RIGHT + 1;
/** The size in bytes of the header data for the node. */
private final static int HEADER_SIZE_B = HEADER_SIZE * Constants.SIZEOF_LONG;
/** The size in bytes of the header data for the node. */
public final static int HEADER_SIZE_I = HEADER_SIZE * 2;
/** The index in bytes within the block of the balance of the node. */
private final static int IDX_BALANCE_B = HEADER_SIZE_B;
/** The index in bytes within the block of the payload of the node. */
public final static int IDX_PAYLOAD_B = HEADER_SIZE_B;
/** The index in integers within the block of the payload of the node. */
public final static int IDX_PAYLOAD_I = HEADER_SIZE_I;
/** The index in longs within the block of the payload of the node. */
public final static int IDX_PAYLOAD = HEADER_SIZE;
/** An ID to indicate that a Node is not valid. */
final static long NULL_NODE = Block.INVALID_BLOCK_ID;
/** The logger. */
@SuppressWarnings("unused")
private final static Logger logger = Logger.getLogger(AVLNode.class);
/** The most recent phase that this node belongs to. */
private AVLFile.Phase phase;
/** The parent node of this node. */
private AVLNode parentNode;
/** Cached copy of left child node. */
private AVLNode leftChildNode;
/** Cached copy of right child node. */
private AVLNode rightChildNode;
/** The index of this node within the parent node. */
private int childIndex;
/** The block containing the data for this node. */
private Block block;
/** Whether or not this node can be modified. */
private boolean writable;
/** Whether or not this node has been modified. */
private boolean dirty;
// private StackTrace trace;
/** The number of references to this node. */
private int refCount;
// TODO we only need one of these per session - move it to the object
// pool.
/** Gets new calculated balances in the left and right children of this node. */
private int[] newBalances = new int[2];
/**
* Creates a new AVLNode for a given AVLFile phase, and data in the file.
*
* @param phase The phase from the AVLFile.
* @param parentNode The parent node to this node.
* @param childIndex The index (left or right) of this node in relation to its parent.
* @param nodeId The ID of this node, used for finding it in the file..
*/
private AVLNode(AVLFile.Phase phase, AVLNode parentNode, int childIndex, long nodeId) {
init(phase, parentNode, childIndex, nodeId);
}
/**
* Constructs a new node for a given AVLFile phase.
*
* @param phase The phase form the AVLFile.
* @throws IOException If there was an I/O exception.
*/
private AVLNode(AVLFile.Phase phase) throws IOException {
init(phase);
}
/**
* Factory method for an AVLNode.
*
* @param phase The phase form the AVLFile.
* @return The new node, backed by a new block in the file.
* @throws IOException If there was an I/O exception.
*/
public static AVLNode newInstance(AVLFile.Phase phase) throws IOException {
if (!phase.isCurrent()) {
throw new IllegalStateException("Attempt to allocate a new AVL node on a read-only phase.");
}
return new AVLNode(phase);
}
/**
* Factory method for an AVLNode.
*
* @param phase The phase form the AVLFile.
* @param parentNode The parent to this node.
* @param childIndex The index of this node within its parent, either left or right.
* @param nodeId The ID to find the data for this node in the file.
* @return The new node, read from file, or with new file info in it.
* @throws IOException If there was an I/O exception.
*/
static AVLNode newInstance(AVLFile.Phase phase, AVLNode parentNode, int childIndex, long nodeId) {
return new AVLNode(phase, parentNode, childIndex, nodeId);
}
/**
* Calculates new balance factors and the change in height of a subtree which
* has undergone a single rotate.
*
* @param balance The balance of the initial root of the subtree.
* @param balanceL The balance of the left subnode when heavy on the left,
* or the right subnode when heavy on the right.
* @param newBalances new balances for the left and right subnodes.
* @return The balance of th new root of the subtree.
*/
private static int calcNewBalances(
int balance, int balanceL, int[] newBalances
) {
// variable names presume being heavy on the left.
// invert the names when heavy on the right.
int heightL = relHeight(balance);
int heightR = relHeight( -balance);
int heightLL = heightL + relHeight(balanceL);
int heightLR = heightL + relHeight( -balanceL);
int newHeightR = Math.max(heightLR, heightR) + 1;
int newHeight = Math.max(heightLL, newHeightR) + 1;
newBalances[0] = heightLL - newHeightR;
newBalances[1] = heightLR - heightR;
return newHeight;
}
/**
* Get a relative height for children nodes, based on a balance factor.
*
* @param balance The balance factor for a node.
* @return A relative height value, based on balances.
*/
private static int relHeight(int balance) {
return (balance < 0 ? balance : 0) - 1;
}
/**
* Walks up the tree to get a reference to the root node corresponding
* to the given node. The refCount is not incremented so the caller
* should call incRefCount() if the lifetime of the returned root node
* should be longer than that of the given node.
*
* @param node the given node.
* @return the root node corresponding to the given node.
*/
public static AVLNode getRootNode(AVLNode node) {
if (node != null) {
while (node.parentNode != null) node = node.parentNode;
}
return node;
}
/**
* Returns an AVLNode array with a single element (the found node) if the
* node was found or with two elements if the node was not found. If the node
* is not found then the nodes returned are the nodes that would preceed and
* follow the node if it existed in the tree. One of the two elements is
* either a leaf node or the parent of a leaf node and the other node may be
* null.
*
* @param node the starting node.
* @param comparator the comparator to use to compare a key and an AVLNode.
* @param key the key.
* @return the one or two element array of AVLNodes.
*/
public static AVLNode[] find(
AVLNode node, AVLComparator comparator, long[] key
) {
if (node == null) return null;
if (comparator.compare(key, node) == 0) {
node.incRefCount();
return new AVLNode[] {node};
}
// Walk to the root.
while (node.parentNode != null) {
node = node.parentNode;
}
return findDown(node, comparator, key);
}
/**
* Returns an AVLNode array with a single element (the found node) if the
* node was found or with two elements if the node was not found. If the node
* is not found then the nodes returned are the nodes that would preceed and
* follow the node if it existed in the tree. One of the two elements is
* either a leaf node or the parent of a leaf node and the other node may be
* null.
*
* @param node the starting node.
* @param comparator the comparator to use to compare a key and an AVLNode.
* @param key the key.
* @return the one or two element array of AVLNodes.
*/
static AVLNode[] findDown(
AVLNode node, AVLComparator comparator, long[] key
) {
assert node != null;
node.incRefCount();
// Now search down the tree.
AVLNode nextNode;
AVLNode lastLeftChildNode = null;
AVLNode lastRightChildNode = null;
int c;
while ((c = comparator.compare(key, node)) != 0) {
if (c < 0) {
if ((nextNode = node.getLeftChildNode_N()) == null) {
if (lastRightChildNode != null) {
lastRightChildNode.incRefCount();
}
return new AVLNode[] {lastRightChildNode, node};
}
lastLeftChildNode = node;
} else {
if ((nextNode = node.getRightChildNode_N()) == null) {
if (lastLeftChildNode != null) {
lastLeftChildNode.incRefCount();
}
return new AVLNode[] {node, lastLeftChildNode};
}
lastRightChildNode = node;
}
node = nextNode;
}
return new AVLNode[] {node};
}
/**
* Gets the Id attribute of the AVLNode object
*
* @return The Id value
*/
public long getId() {
return block.getBlockId();
}
/**
* Gets the LeafNode attribute of the AVLNode object
*
* @return The LeafNode value
*/
public boolean isLeafNode() {
return (block.getLong(IDX_LEFT) == NULL_NODE) &&
(block.getLong(IDX_RIGHT) == NULL_NODE);
}
/**
* Gets the ParentNode attribute of the AVLNode object and releases the current node.
* @deprecated
*
* @return The ParentNode value
*/
public AVLNode getParentNode_R() {
AVLNode node = getParentNode();
release();
return node;
}
/**
* Gets the ParentNode attribute of the AVLNode object
*
* @return The ParentNode value
*/
public AVLNode getParentNode() {
if (parentNode != null) parentNode.incRefCount();
return parentNode;
}
/**
* Returns the left child node or null if there is no left child node. The
* current node is released.
*
* @return the left child node or null if there is no left child node.
*/
public AVLNode getLeftChildNode_R() {
AVLNode node = getLeftChildNode_N();
if (node == null) {
release();
}
return node;
}
/**
* Returns the left child node or null if there is no left child node. The
* current node is not released.
*
* @return the left child node or null if there is no left child node.
*/
public AVLNode getLeftChildNode() {
assert refCount > 0;
AVLNode node = leftChildNode;
if (node != null) {
assert node.parentNode == this;
assert node.getId() == getLeftId();
node.incRefCount();
return node;
}
node = getChildNode_N(IDX_LEFT);
leftChildNode = node;
if (node != null) {
incRefCount();
}
return node;
}
/**
* Returns the right child node or null if there is no right child node. The
* current node is released.
*
* @return the right child node or null if there is no right child node.
*/
public AVLNode getRightChildNode_R() {
AVLNode node = getRightChildNode_N();
if (node == null) {
release();
}
return node;
}
/**
* Returns the right child node or null if there is no right child node. The
* current node is not released.
*
* @return the right child node or null if there is no right child node.
*/
public AVLNode getRightChildNode() {
assert refCount > 0;
AVLNode node = rightChildNode;
if (node != null) {
assert node.parentNode == this;
assert node.getId() == getRightId();
node.incRefCount();
return node;
}
node = getChildNode_N(IDX_RIGHT);
rightChildNode = node;
if (node != null) {
incRefCount();
}
return node;
}
/**
* Gets the PrevNode attribute of the AVLNode object. The current node is released.
*
* @return The PrevNode_R value
*/
public AVLNode getPrevNode_R() {
AVLNode node = getPrevNode();
release();
return node;
}
/**
* Gets the PrevNode attribute of the AVLNode object. The current node is not released.
*
* @return The PrevNode value
*/
public AVLNode getPrevNode() {
AVLNode node;
if ((node = getLeftChildNode()) != null) {
return node.getMaxNode_R();
}
node = this;
while (node.childIndex == IDX_LEFT) {
if ((node = node.parentNode) == null) {
return null;
}
}
node = node.parentNode;
if (node != null) {
node.incRefCount();
}
return node;
}
/**
* Gets the NextNode attribute of the AVLNode object. The current node is released.
*
* @return The NextNode_R value
*/
public AVLNode getNextNode_R() {
AVLNode node = getNextNode();
release();
return node;
}
/**
* Gets the NextNode attribute of the AVLNode object. The current node is not released.
*
* @return The NextNode value
*/
public AVLNode getNextNode() {
AVLNode node;
if ((node = getRightChildNode()) != null) {
return node.getMinNode_R();
}
node = this;
while (node.childIndex == IDX_RIGHT) {
if ((node = node.parentNode) == null) {
return null;
}
}
node = node.parentNode;
if (node != null) {
node.incRefCount();
}
return node;
}
/**
* Gets the Block of the AVLNode object
*
* @return The Block value
*/
public Block getBlock() {
return block;
}
/**
* Gets an integer from the AVLNode payload
*
* @param offset The offset into the payload
* @return The requested integer value
*/
public int getPayloadInt(int offset) {
assert offset > 0;
return block.getInt(IDX_PAYLOAD_I + offset);
}
/**
* Gets an unsigned integer from the AVLNode payload
*
* @param offset The offset into the payload
* @return The requested unsigned integer value
*/
public long getPayloadUInt(int offset) {
assert offset > 0;
return block.getUInt(IDX_PAYLOAD_I + offset);
}
/**
* Gets a long from the AVLNode payload
*
* @param offset The offset into the payload (in longs).
* @return The requested long value.
*/
public long getPayloadLong(int offset) {
assert offset > 0;
return block.getLong(IDX_PAYLOAD + offset);
}
/**
* Gets a byte from the AVLNode payload
*
* @param offset The offset into the payload (in bytes).
* @return The requested byte value
*/
public int getPayloadByte(int offset) {
assert offset > 0;
return block.getByte(IDX_PAYLOAD_B + offset);
}
/**
* Gets the minimum node to the left of this AVLNode (the smallest node
* in this subtree). The current node has its reference released.
*
* @return The node under this node containing the smallest values, or
* the current node if this is the smallest.
*/
public AVLNode getMinNode_R() {
AVLNode node = this;
AVLNode nextNode;
while ((nextNode = node.getLeftChildNode_N()) != null) {
node = nextNode;
}
return node;
}
/**
* Gets the minimum node to the left of this AVLNode (the smallest node
* in this subtree). The current node is not released.
*
* @return The node under this node containing the smallest values, or
* the current node if this is the smallest.
*/
public AVLNode getMinNode() {
incRefCount();
return getMinNode_R();
}
/**
* Gets the maximum node to the right of this AVLNode (the largest node
* in this subtree). The current node has its reference released.
*
* @return The node under this node containing the largest values, or
* the current node if this is the largest.
*/
public AVLNode getMaxNode_R() {
AVLNode node = this;
AVLNode nextNode;
while ((nextNode = node.getRightChildNode_N()) != null) {
node = nextNode;
}
return node;
}
/**
* Gets the maximum node to the right of this AVLNode (the largest node
* in this subtree). The current node is not released.
*
* @return The node under this node containing the largest values, or
* the current node if this is the largest.
*/
public AVLNode getMaxNode() {
incRefCount();
return getMaxNode_R();
}
/**
* Gets the Height attribute of the AVLNode object
*
* @return The Height value
*/
public int getHeight() {
AVLNode lChildNode = getLeftChildNode();
int leftHeight = (lChildNode != null) ? lChildNode.getHeight() : 0;
if (lChildNode != null) {
lChildNode.release();
}
AVLNode rChildNode = getRightChildNode();
int rightHeight = (rChildNode != null) ? rChildNode.getHeight() : 0;
if (rChildNode != null) {
rChildNode.release();
}
// Check the balance value.
int balance = leftHeight - rightHeight;
if (balance != block.getByte(IDX_BALANCE_B)) {
throw new RuntimeException(
"Incorrect balance for node " + getId() +
". Expected: " + balance + " but was: " +
block.getByte(IDX_BALANCE_B)
);
}
return Math.max(leftHeight, rightHeight) + 1;
}
/**
* Increment the reference count of this node.
* This means that the node will need to be released one
* more time before it can be considered unused.
*/
public void incRefCount() {
assert refCount > 0;
++refCount;
}
/**
* If the block belongs to an older phase, move it to the new phase
* by creating a copy.
*
* @throws IOException If an I/O error occurred.
*/
public void modify() throws IOException {
assert refCount > 0;
if (writable) {
dirty = true;
return;
}
if (!phase.isCurrent()) {
throw new IllegalStateException(
"Attempt to modify a node for a read-only phase."
);
}
long id = block.getBlockId();
block.modify();
writable = true;
dirty = true;
long newId = block.getBlockId();
if (newId != id) {
if (parentNode != null) {
parentNode.modify();
parentNode.block.putLong(childIndex, newId);
} else {
phase.setRootId(newId);
}
}
}
/**
* Puts an integer into the payload of this node.
*
* @param offset The offset into the payload, in ints.
* @param i The value to put into the payload.
*/
public void putPayloadInt(int offset, int i) {
assert dirty;
assert offset > 0;
block.putInt(IDX_PAYLOAD_I + offset, i);
}
/**
* Puts an unsigned integer into the payload of this node.
*
* @param offset The offset into the payload, in ints.
* @param ui The unsigned integer value to put into the payload.
*/
public void putPayloadUInt(int offset, long ui) {
assert dirty;
assert offset > 0;
block.putUInt(IDX_PAYLOAD_I + offset, ui);
}
/**
* Puts a long into the payload of this node.
*
* @param offset The offset into the payload, in longs.
* @param l The value to put into the payload.
*/
public void putPayloadLong(int offset, long l) {
assert dirty;
assert offset > 0;
block.putLong(IDX_PAYLOAD + offset, l);
}
/**
* Puts an array of longs into the payload of this node.
*
* @param offset The offset into the payload for the beginning
* of the array, in longs.
* @param la The array to put into the payload.
*/
public void putPayload(int offset, long[] la) {
assert dirty;
assert offset > 0;
block.put(IDX_PAYLOAD + offset, la);
}
/**
* Puts a byte into the payload of this node.
*
* @param offset The offset into the payload, in bytes.
* @param b The value to put into the payload.
*/
public void putPayloadByte(int offset, byte b) {
assert dirty;
assert offset > 0;
block.putByte(IDX_PAYLOAD_B + offset, b);
}
// The following methods work on the current phase tree
/**
* Inserts an AVLNode into the current phase tree, under the current node.
* The current node must not have a child under it on the <em>ci</em> side.
*
* @param newNode The node to insert.
* @param ci The index for insertion - {@link #IDX_LEFT} or {@link #IDX_RIGHT}
* @throws IOException If an I/O error occurred.
*/
public void insert(AVLNode newNode, int ci) throws IOException {
if (newNode.parentNode != null) {
throw new IllegalArgumentException("newNode already has parent node.");
}
if (ci != 0 && ci != 1) {
throw new IllegalArgumentException("ci: " + ci);
}
if (block.getLong(ci) != NULL_NODE) {
throw new IllegalStateException("Non-leaf inserts are illegal.");
}
modify();
newNode.parentNode = this;
newNode.childIndex = ci;
incRefCount();
if (ci == IDX_LEFT) {
leftChildNode = newNode;
} else {
rightChildNode = newNode;
}
block.putLong(ci, newNode.getId());
rebalanceInsert(ci);
phase.incNrNodes();
}
/**
* Remove this node from the AVL Tree.
*
* @throws IOException An I/O error occurred.
*/
public void remove() throws IOException {
assert refCount == 1;
if (isLeafNode()) {
assert leftChildNode == null;
assert rightChildNode == null;
if (parentNode == null) {
phase.setRootId(NULL_NODE);
} else {
AVLNode node = parentNode;
node.modify();
parentNode = null;
if (childIndex == IDX_LEFT) {
node.leftChildNode = null;
} else {
node.rightChildNode = null;
}
node.block.putLong(childIndex, NULL_NODE);
node.rebalanceRemove(childIndex);
node.release();
}
} else {
// Move an adjacent node to replace this node in the tree.
AVLNode childNode;
AVLNode adjacentNode;
int ci;
if (block.getByte(IDX_BALANCE_B) > 0) {
childNode = getLeftChildNode();
adjacentNode = childNode.getMaxNode_R();
ci = IDX_LEFT;
} else {
childNode = getRightChildNode();
adjacentNode = childNode.getMinNode_R();
ci = IDX_RIGHT;
}
// Save adjacent node pointers.
long adjacentNodeChildId = adjacentNode.block.getLong(ci);
AVLNode adjacentParent = adjacentNode.parentNode;
int adjacentParentChildIndex = adjacentNode.childIndex;
// Replace this node in the tree with the adjacent node.
adjacentNode.modify();
adjacentNode.parentNode = parentNode;
adjacentNode.leftChildNode = null;
adjacentNode.rightChildNode = null;
adjacentNode.childIndex = childIndex;
adjacentNode.block.putLong(IDX_LEFT, block.getLong(IDX_LEFT));
adjacentNode.block.putLong(IDX_RIGHT, block.getLong(IDX_RIGHT));
adjacentNode.block.putByte(IDX_BALANCE_B, block.getByte(IDX_BALANCE_B));
if (parentNode == null) {
phase.setRootId(adjacentNode.getId());
} else {
if (childIndex == IDX_LEFT) {
parentNode.leftChildNode = adjacentNode;
} else {
parentNode.rightChildNode = adjacentNode;
}
parentNode.modify();
parentNode.block.putLong(childIndex, adjacentNode.getId());
}
assert refCount == 2;
assert adjacentNode.refCount == 1;
parentNode = null;
leftChildNode = null;
rightChildNode = null;
refCount = 1;
if (childNode != adjacentNode) {
childNode.parentNode = adjacentNode;
if (ci == IDX_LEFT) {
adjacentNode.leftChildNode = childNode;
} else {
adjacentNode.rightChildNode = childNode;
}
// Reparent the child of adjacentNode if any.
if (adjacentParentChildIndex == IDX_LEFT) {
adjacentParent.leftChildNode = null;
} else {
adjacentParent.rightChildNode = null;
}
adjacentParent.modify();
adjacentParent.block.putLong(
adjacentParentChildIndex, adjacentNodeChildId
);
adjacentParent.rebalanceRemove(adjacentParentChildIndex);
adjacentParent.release();
} else {
adjacentNode.modify();
adjacentNode.block.putLong(
adjacentParentChildIndex, adjacentNodeChildId
);
adjacentNode.rebalanceRemove(ci);
adjacentNode.release();
}
}
phase.decNrNodes();
free();
}
/**
* Writes the Block for this node to the file.
*
* @throws IOException If an I/O error occurs.
*/
public void write() throws IOException {
if (!writable) {
throw new IllegalStateException("AVLNode not writable");
}
if (dirty) {
block.write();
dirty = false;
}
}
/**
* Release a reference to this AVLNode. When there are no references
* left the block is written back to disk, and the node is put back
* into the node pool.
*/
public void release() {
AVLNode avlNode = this;
do {
assert avlNode.refCount > 0;
if (--avlNode.refCount > 0) return;
assert avlNode.leftChildNode == null;
assert avlNode.rightChildNode == null;
avlNode.phase = null;
if (avlNode.block != null) {
if (avlNode.writable) {
try {
avlNode.write();
} catch (IOException ex) {
throw new Error("IOException", ex);
}
}
avlNode.block = null;
}
avlNode.writable = false;
//X avlNode.trace = null;
AVLNode prevNode = avlNode;
avlNode = avlNode.parentNode;
prevNode.parentNode = null;
if (avlNode != null) {
if (prevNode.childIndex == IDX_LEFT) {
avlNode.leftChildNode = null;
} else {
avlNode.rightChildNode = null;
}
}
}
while (avlNode != null);
}
/**
* Gets a string representation of the subtree rooted at this node.
*
* @return A string showing the structure of the tree.
*/
public String toString() {
StringBuffer sb = new StringBuffer();
toString(sb);
return sb.toString();
}
/**
* Gets the ID of the left child node to this node.
*
* @return The ID of the left child node.
*/
long getLeftId() {
return block.getLong(IDX_LEFT);
}
/**
* Gets the ID of the right child node to this node.
*
* @return The ID of the right child node.
*/
long getRightId() {
return block.getLong(IDX_RIGHT);
}
/**
* Gets the Balance of the AVLNode
*
* @return The Balance value
*/
int getBalance() {
return block.getByte(IDX_BALANCE_B);
}
/**
* Returns the left child node or null if there is no left child node. The
* current node is released if and only if there is a left child node.
*
* @return the left child node or null if there is no left child node.
*/
private AVLNode getLeftChildNode_N() {
AVLNode node = leftChildNode;
if (node != null) {
assert refCount > 1;
assert node.parentNode == this;
assert node.getId() == getLeftId();
node.incRefCount();
--refCount;
return node;
}
assert refCount > 0;
node = getChildNode_N(IDX_LEFT);
leftChildNode = node;
return node;
}
/**
* Returns the right child node or null if there is no right child node. The
* current node is released if and only if there is a right child node.
*
* @return the right child node or null if there is no right child node.
*/
private AVLNode getRightChildNode_N() {
AVLNode node = rightChildNode;
if (node != null) {
assert refCount > 1;
assert node.parentNode == this;
assert node.getId() == getRightId();
node.incRefCount();
--refCount;
return node;
}
assert refCount > 0;
node = getChildNode_N(IDX_RIGHT);
rightChildNode = node;
return node;
}
/**
* Gets the ChildNode on a given side of the current node.
*
* @param index The index of the child. Either {@link #IDX_LEFT}
* or {@link #IDX_RIGHT}
* @return The Child node, or <code>null</code> if there is no
* child on the given side.
*/
private AVLNode getChildNode_N(int index) {
assert refCount > 0;
long nodeId = block.getLong(index);
return nodeId == NULL_NODE ? null : newInstance(phase, this, index, nodeId);
}
/**
* Initialises a node on the given information.
*
* @param phase The phase that the node exists in.
* @param parentNode The parent node for this node. Only
* <code>null</code> at the root of the tree.
* @param childIndex Indicates if this is to the left or the
* right of the parent node. Either {@link #IDX_LEFT} or
* {@link #IDX_RIGHT}.
* @param nodeId This ID for this node.
*/
private void init(AVLFile.Phase phase, AVLNode parentNode, int childIndex, long nodeId) {
this.phase = phase;
this.parentNode = parentNode;
this.childIndex = childIndex;
this.leftChildNode = null;
this.rightChildNode = null;
//X trace = new StackTrace();
refCount = 1;
writable = false;
dirty = false;
try {
block = phase.getAVLBlockFilePhase().readBlock(nodeId);
} catch (IOException ex) {
throw new Error("IOException", ex);
}
}
/**
* Initialises a node on the given information.
*
* @param phase The phase that the node exists in.
* @throws IOException If an I/O error occurs.
*/
private void init(AVLFile.Phase phase) throws IOException {
this.phase = phase;
parentNode = null;
childIndex = 0;
this.leftChildNode = null;
this.rightChildNode = null;
//X trace = new StackTrace();
refCount = 1;
writable = true;
dirty = true;
block = phase.getAVLBlockFilePhase().allocateBlock();
block.putLong(IDX_LEFT, NULL_NODE);
block.putLong(IDX_RIGHT, NULL_NODE);
block.putByte(IDX_BALANCE_B, (byte) 0);
}
/**
* Frees all resources from this node, and releases its reference.
* This should be the last reference to the node.
*
* @throws IOException If an I/O error occurs.
*/
private void free() throws IOException {
writable = false;
dirty = false;
block.free();
block = null;
release();
}
/**
* Perform a rebalance from this node down, after having done an insert.
*
* @param ci The index of this node in its parent. Either {@link #IDX_LEFT}
* or {@link #IDX_RIGHT}.
* @throws IOException If an I/O error occurs.
*/
private void rebalanceInsert(int ci) throws IOException {
AVLNode node = this;
int balance;
do {
// Assumes ci is 0 for left and 1 for right.
balance = (node.block.getByte(IDX_BALANCE_B) + 1) - (ci * 2);
node.modify();
node.block.putByte(IDX_BALANCE_B, (byte) balance);
if (balance == 2) {
node.incRefCount();
node.rotateL();
node.release();
break;
} else if (balance == -2) {
node.incRefCount();
node.rotateR();
node.release();
break;
}
ci = node.childIndex;
// break if balance is zero
}
while (balance != 0 && (node = node.parentNode) != null);
}
/**
* Perform a rebalance from this node up, after having removed a node.
*
* @param ci The index of this node in its parent. Either {@link #IDX_LEFT}
* or {@link #IDX_RIGHT}.
* @throws IOException If an I/O error occurs.
*/
private void rebalanceRemove(int ci) throws IOException {
AVLNode node = this;
int balance;
do {
// Assumes ci is 0 for left and 1 for right.
balance = node.block.getByte(IDX_BALANCE_B) - (1 - (ci * 2));
node.modify();
node.block.putByte(IDX_BALANCE_B, (byte) balance);
if (balance == 2) {
node.incRefCount();
int deltaHeight = node.rotateL();
node.release();
if (deltaHeight == 0) {
break;
}
// Continue processing for a remove.
node = node.parentNode;
balance = 0;
} else if (balance == -2) {
node.incRefCount();
int deltaHeight = node.rotateR();
node.release();
if (deltaHeight == 0) {
break;
}
// Continue processing for a remove.
node = node.parentNode;
balance = 0;
}
ci = node.childIndex;
// break if balance is non-zero
}
while (balance == 0 && (node = node.parentNode) != null);
}
/**
* The rotate which is performed when the node is heavy on the left
* sub-branch.
*
* @return the change in height of the node.
* @throws IOException If an I/O error occurs.
*/
private int rotateL() throws IOException {
AVLNode nodeL = getLeftChildNode();
if (nodeL == null) throw new IllegalStateException("Invalid tree structure on disk");
int deltaHeight = (nodeL.block.getByte(IDX_BALANCE_B) >= 0) ?
rotateLL(nodeL) : rotateLR(nodeL);
nodeL.release();
return deltaHeight;
}
/**
* The rotate which is performed when the node is heavy on the right
* sub-branch.
*
* @return the change in height of the node.
* @throws IOException If an I/O error occurs.
*/
private int rotateR() throws IOException {
AVLNode nodeR = getRightChildNode();
if (nodeR == null) throw new IllegalStateException("Invalid tree structure on disk");
int deltaHeight = (nodeR.block.getByte(IDX_BALANCE_B) <= 0) ?
rotateRR(nodeR) : rotateRL(nodeR);
nodeR.release();
return deltaHeight;
}
/**
* The rotate which is performed when the node is heavy on the left child's
* left sub-branch.
*
* @param nodeL a reference to the left child.
* @return the change in height of the node.
* @throws IOException If an I/O error occurs.
*/
private int rotateLL(AVLNode nodeL) throws IOException {
assert refCount > 1;
assert nodeL.parentNode == this;
assert leftChildNode == nodeL;
assert nodeL.getId() == getLeftId();
leftChildNode = nodeL.rightChildNode;
nodeL.rightChildNode = this;
nodeL.modify();
modify();
block.putLong(IDX_LEFT, nodeL.block.getLong(IDX_RIGHT));
nodeL.block.putLong(IDX_RIGHT, getId());
// Change the parent node's child pointer.
if (parentNode != null) {
if (childIndex == IDX_LEFT) {
parentNode.leftChildNode = nodeL;
} else {
parentNode.rightChildNode = nodeL;
}
parentNode.modify();
parentNode.block.putLong(childIndex, nodeL.getId());
}
if (leftChildNode != null) {
leftChildNode.parentNode = this;
leftChildNode.childIndex = IDX_LEFT;
} else {
--refCount;
nodeL.incRefCount();
}
nodeL.parentNode = parentNode;
nodeL.childIndex = childIndex;
parentNode = nodeL;
childIndex = IDX_RIGHT;
// Recalculate the balances.
int deltaHeight = calcNewBalances(
block.getByte(IDX_BALANCE_B), nodeL.block.getByte(IDX_BALANCE_B),
newBalances
);
nodeL.block.putByte(IDX_BALANCE_B, (byte) newBalances[0]);
block.putByte(IDX_BALANCE_B, (byte) newBalances[1]);
if (nodeL.parentNode == null) {
phase.setRootId(nodeL.getId());
}
return deltaHeight;
}
/**
* The rotate which is performed when the node is heavy on the right child's
* right sub-branch.
*
* @param nodeR a reference to the right child.
* @return the change in height of the node.
* @throws IOException If an I/O error occurs.
*/
private int rotateRR(AVLNode nodeR) throws IOException {
assert refCount > 1;
assert nodeR.parentNode == this;
assert rightChildNode == nodeR;
assert nodeR.getId() == getRightId();
rightChildNode = nodeR.leftChildNode;
nodeR.leftChildNode = this;
nodeR.modify();
modify();
block.putLong(IDX_RIGHT, nodeR.block.getLong(IDX_LEFT));
nodeR.block.putLong(IDX_LEFT, getId());
// Change the parent node's child pointer.
if (parentNode != null) {
if (childIndex == IDX_LEFT) {
parentNode.leftChildNode = nodeR;
} else {
parentNode.rightChildNode = nodeR;
}
parentNode.modify();
parentNode.block.putLong(childIndex, nodeR.getId());
}
if (rightChildNode != null) {
rightChildNode.parentNode = this;
rightChildNode.childIndex = IDX_RIGHT;
} else {
--refCount;
nodeR.incRefCount();
}
nodeR.parentNode = parentNode;
nodeR.childIndex = childIndex;
parentNode = nodeR;
childIndex = IDX_LEFT;
// Recalculate the balances.
int deltaHeight = calcNewBalances(
-block.getByte(IDX_BALANCE_B), -nodeR.block.getByte(IDX_BALANCE_B),
newBalances
);
nodeR.block.putByte(IDX_BALANCE_B, (byte) - newBalances[0]);
block.putByte(IDX_BALANCE_B, (byte) - newBalances[1]);
if (nodeR.parentNode == null) {
phase.setRootId(nodeR.getId());
}
return deltaHeight;
}
/**
* The rotate which is performed when the node is heavy on the left child's
* right sub-branch.
*
* @param nodeL a reference to the left child.
* @return the change in height of the node.
* @throws IOException If an I/O error occurs.
*/
private int rotateLR(AVLNode nodeL) throws IOException {
// First perform rotateRR on nodeL.
AVLNode nodeLR = nodeL.getRightChildNode();
if (nodeLR == null) throw new IllegalStateException("Invalid tree structure on disk");
int deltaHeightL = nodeL.rotateRR(nodeLR);
// Adjust the balance of this node.
if (deltaHeightL != 0) {
modify();
block.putByte(
IDX_BALANCE_B, (byte)(block.getByte(IDX_BALANCE_B) + deltaHeightL)
);
}
// Finally do a rotateLL on this node.
int deltaHeight = deltaHeightL + rotateLL(nodeLR);
nodeLR.release();
return deltaHeight;
}
/**
* The rotate which is performed when the node is heavy on the right child's
* left sub-branch.
*
* @param nodeR a reference to the right child.
* @return the change in height of the node.
* @throws IOException If an I/O error occurs.
*/
private int rotateRL(AVLNode nodeR) throws IOException {
// First perform rotateLL on nodeR.
AVLNode nodeRL = nodeR.getLeftChildNode();
int deltaHeightR = nodeR.rotateLL(nodeRL);
// Adjust the balance of this node.
if (deltaHeightR != 0) {
modify();
block.putByte(
IDX_BALANCE_B, (byte)(block.getByte(IDX_BALANCE_B) - deltaHeightR)
);
}
// Finally do a rotateRR on this node.
int deltaHeight = deltaHeightR + rotateRR(nodeRL);
nodeRL.release();
return deltaHeight;
}
/**
* Writes a string representation of this tree structure into a
* {@link java.lang.StringBuffer}.
*
* @param sb The string buffer to write to.
*/
private void toString(StringBuffer sb) {
sb.append(getId());
sb.append(" [");
AVLNode tmp;
if ((tmp = getLeftChildNode()) == null) {
sb.append("-");
} else {
tmp.toString(sb);
}
sb.append(", ");
if ((tmp = getRightChildNode()) == null) {
sb.append("-");
} else {
tmp.toString(sb);
}
sb.append("]");
}
//X protected void finalize() {
//X if (trace != null) logger.warn(
//X "Unpooled AVLNode. refCount=" + refCount + ", dirty=" + dirty + "\n " + trace
//X );
//X }
}