/**
* Copyright 2008 Google Inc.
*
* Licensed 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.waveprotocol.wave.model.util;
import java.util.Iterator;
/**
* An offset list that has the capability of evaluating to a value.
*
* TODO(user): Consider reducing recomputation during evaluation to the
* minimum possible.
*
*
* @param <T> The type of data contained in the data structure.
* @param <V> The result of evaluating this data structure.
*/
public final class EvaluableOffsetList<T, V> implements OffsetList<T> {
/**
* An associative operator.
*
* @param <T> The type of the base data.
* @param <V> The type of data handled by the associative operator.
*/
public interface AssociativeOperator<T, V> {
/**
* Extracts a <code>V</code> from a <code>T</code>.
*
* @param value The <code>T</code> from which the <code>V</code> is to be
* extracted.
* @return The extracted value.
*/
V extract(T value);
/**
* Applies the operator to the two given operands.
*
* @param operand1 The first operand.
* @param operand2 The second operand.
* @return The result of applying the operator on the two given operands.
*/
V operate(V operand1, V operand2);
}
/**
* A container for holding data.
*
* @param <T> The type of data contained in the container.
*/
public interface Container<T> extends OffsetList.Container<T> {
@Override
Container<T> getPreviousContainer();
@Override
Container<T> getNextContainer();
@Override
Container<T> insertBefore(T newValue, int valueSize);
@Override
Container<T> split(int offset, T newValue);
/**
* Invalidate the cached evaluation of this container, to indicate that the
* evaluation of this container should be recomputed the next time its
* offset list is evaluated.
*/
void invalidate();
}
/**
* A container, implemented as a node in a tree.
*
* @param <T> The type of data contained in the container.
*/
private static final class Node<T, V> implements Container<T> {
/**
* The value contained in this container.
*/
private T value;
/**
* The cached evaluation of this node.
*/
private V subtreeComputation;
/**
* The parent of this node
*/
private Node<T, V> parent;
/**
* The left child of this node.
*/
private Node<T, V> leftChild;
/**
* The right child of this node.
*/
private Node<T, V> rightChild;
/**
* The container before this container.
*/
private Node<T, V> previousContainer;
/**
* The container after this container.
*/
private Node<T, V> nextContainer;
/**
* The offset of this node in its subtree.
*/
private int offset;
/**
* The size of this node.
*/
private int size;
/**
* The height of this node.
*/
private int height;
private Node(T value, int size) {
assert size >= 0;
this.value = value;
this.size = size;
}
@Override
public Container<T> getPreviousContainer() {
return previousContainer;
}
@Override
public Container<T> getNextContainer() {
return nextContainer;
}
@Override
public T getValue() {
return value;
}
@Override
public void setValue(T value) {
this.value = value;
}
@Override
public int offset() {
int totalOffset = offset;
for (Node<T, V> currentNode = this, currentParent = parent; currentParent != null;
currentParent = currentNode.parent) {
if (currentParent.rightChild == currentNode) {
totalOffset += currentParent.offset + currentParent.size;
}
currentNode = currentParent;
}
return totalOffset;
}
@Override
public int size() {
return size;
}
@Override
public Container<T> insertBefore(T newValue, int valueSize) {
Node<T, V> newContainer = new Node<T, V>(newValue, valueSize);
newContainer.previousContainer = previousContainer;
newContainer.nextContainer = this;
previousContainer.nextContainer = newContainer;
previousContainer = newContainer;
if (leftChild == null) {
setLeftChild(newContainer);
} else {
leftChild.getLastInSubtree().setRightChild(newContainer);
}
offset += newContainer.size;
correctOffsets(newContainer.size);
newContainer.parent.rebalance();
return newContainer;
}
@Override
public void remove() {
// Assert that the node to remove is not the sentinel node.
assert parent != null;
nextContainer.previousContainer = previousContainer;
previousContainer.nextContainer = nextContainer;
correctOffsets(-size);
if (rightChild == null) {
replaceWith(leftChild);
parent.rebalance();
} else {
Node<T, V> replacementNode = rightChild.getFirstInSubtree();
Node<T, V> parentOfReplacement = replacementNode.parent;
int removalSize = replacementNode.size;
for (Node<T, V> node = parentOfReplacement; node != this; node = node.parent) {
node.offset -= removalSize;
}
replacementNode.replaceWith(replacementNode.rightChild);
replaceWith(replacementNode);
// Ensure that the replacement node assimilates the left child, right
// child, offset, and height of the node it is replacing.
replacementNode.setLeftChildSafely(leftChild);
replacementNode.setRightChildSafely(rightChild);
replacementNode.offset = offset;
replacementNode.height = height;
parent.invalidate();
((parentOfReplacement != this) ? parentOfReplacement : replacementNode).rebalance();
}
// Help accidental reuse fail quickly
nextContainer = null;
previousContainer = null;
parent = null;
leftChild = null;
rightChild = null;
}
@Override
public Container<T> split(int offset, T newValue) {
assert offset >= 0;
assert offset <= size;
int secondSize = size - offset;
Node<T, V> newContainer = new Node<T, V>(newValue, secondSize);
newContainer.nextContainer = nextContainer;
newContainer.previousContainer = this;
nextContainer.previousContainer = newContainer;
nextContainer = newContainer;
size = offset;
if (rightChild == null) {
setRightChild(newContainer);
} else {
Node<T, V> node = rightChild;
while (node.leftChild != null) {
node.offset += secondSize;
node = node.leftChild;
}
node.offset += secondSize;
node.setLeftChild(newContainer);
}
newContainer.parent.rebalance();
return newContainer;
}
@Override
public void increaseSize(int sizeDelta) {
assert size >= -sizeDelta;
size += sizeDelta;
correctOffsets(sizeDelta);
}
@Override
public void invalidate() {
if (subtreeComputation != null) {
subtreeComputation = null;
if (parent != null) {
parent.invalidate();
}
}
}
/**
* Rebalances the tree along the path from this node up to the root. This
* will also clear the cached evaluations of all nodes along the path to the
* root.
*/
private void rebalance() {
invalidate();
for (Node<T, V> node = this; node.parent != null; node = node.parent) {
int oldHeight = node.height;
int leftHeight = getHeight(node.leftChild);
int rightHeight = getHeight(node.rightChild);
if (rightHeight == leftHeight + 2) {
if (getHeight(node.rightChild.rightChild) != rightHeight - 1) {
node.rightChild.rotateRight();
}
node = node.rotateLeft();
} else if (leftHeight == rightHeight + 2) {
if (getHeight(node.leftChild.leftChild) != leftHeight - 1) {
node.leftChild.rotateLeft();
}
node = node.rotateRight();
} else {
node.height = Math.max(leftHeight, rightHeight) + 1;
}
if (node.height == oldHeight) {
break;
}
}
}
/**
* Performs a left rotation around this node.
*
* @return The new root of the subtree involved in the rotation.
*/
private Node<T, V> rotateLeft() {
Node<T, V> newRoot = rightChild;
Node<T, V> subtree1 = newRoot.leftChild;
Node<T, V> subtree2 = leftChild;
replaceWith(newRoot);
setRightChildSafely(subtree1);
newRoot.setLeftChild(this);
height = Math.max(getHeight(subtree1), getHeight(subtree2)) + 1;
if (height + 1 > newRoot.height) {
newRoot.height = height + 1;
}
newRoot.offset += offset + size;
subtreeComputation = null;
newRoot.subtreeComputation = null;
return newRoot;
}
/**
* Performs a right rotation around this node.
*
* @return The new root of the subtree involved in the rotation.
*/
private Node<T, V> rotateRight() {
Node<T, V> newRoot = leftChild;
Node<T, V> subtree1 = newRoot.rightChild;
Node<T, V> subtree2 = rightChild;
replaceWith(newRoot);
setLeftChildSafely(subtree1);
newRoot.setRightChild(this);
height = Math.max(getHeight(subtree1), getHeight(subtree2)) + 1;
if (height + 1 > newRoot.height) {
newRoot.height = height + 1;
}
offset -= newRoot.offset + newRoot.size;
subtreeComputation = null;
newRoot.subtreeComputation = null;
return newRoot;
}
/**
* Alters the offsets along the path from this node to the root.
*
* @param sizeDelta The size of the correction.
*/
private void correctOffsets(int sizeDelta) {
for (Node<T, V> node = this, parentNode = parent; parentNode != null;
node = parentNode, parentNode = node.parent) {
if (parentNode.leftChild == node) {
parentNode.offset += sizeDelta;
}
}
}
/**
* Gets the first node in the subtree rooted at this node.
*
* @return The first node in the subtree rooted at this node.
*/
private Node<T, V> getFirstInSubtree() {
Node<T, V> node = this;
for (Node<T, V> child = leftChild; child != null; node = child, child = node.leftChild) {}
return node;
}
/**
* Gets the last node in the subtree rooted at this node.
*
* @return The last node in the subtree rooted at this node.
*/
private Node<T, V> getLastInSubtree() {
Node<T, V> node = this;
for (Node<T, V> child = rightChild; child != null; node = child, child = node.rightChild) {}
return node;
}
/**
* Sets the left child of this node, ensuring that the parent-child
* relationship is correctly registered in both the parent and the child.
* This assumes that the input argument is not null.
*
* @param leftChild The node which should become the left child of this
* node. This should not be null.
*/
private void setLeftChild(Node<T, V> leftChild) {
assert leftChild != null;
this.leftChild = leftChild;
leftChild.parent = this;
}
/**
* Sets the right child of this node, ensuring that the parent-child
* relationship is correctly registered in both the parent and the child.
* This assumes that the input argument is not null.
*
* @param rightChild The node which should become the right child of this
* node. This should not be null.
*/
private void setRightChild(Node<T, V> rightChild) {
assert rightChild != null;
this.rightChild = rightChild;
rightChild.parent = this;
}
/**
* Sets the left child of this node, ensuring that the parent-child
* relationship is correctly registered in both the parent and the child.
*
* @param leftChild The node which should become the right child of this
* node. This may be null to indicate that this node should have no
* left child.
*/
private void setLeftChildSafely(Node<T, V> leftChild) {
this.leftChild = leftChild;
if (leftChild != null) {
leftChild.parent = this;
}
}
/**
* Sets the right child of this node, ensuring that the parent-child
* relationship is correctly registered in both the parent and the child.
*
* @param rightChild The node which should become the right child of this
* node. This may be null to indicate that this node should have no
* right child.
*/
private void setRightChildSafely(Node<T, V> rightChild) {
this.rightChild = rightChild;
if (rightChild != null) {
rightChild.parent = this;
}
}
/**
* Replaces the subtree rooted at this node with another subtree.
*
* @param replacement The root of the replacement subtree.
*/
private void replaceWith(Node<T, V> replacement) {
assert parent != null;
if (replacement != null) {
replacement.parent = parent;
}
if (parent.leftChild == this) {
parent.leftChild = replacement;
} else {
parent.rightChild = replacement;
}
}
@Override
public String toString() {
return offset() + "," + size() + ":" + getValue();
}
}
private final Node<T, V> root;
private final AssociativeOperator<? super T, V> operator;
/**
* Constructs a new offset list.
*/
public EvaluableOffsetList() {
this(null);
}
/**
* Constructs a new offset list.
*/
public EvaluableOffsetList(AssociativeOperator<? super T, V> operator) {
root = new Node<T, V>(null, 1);
root.previousContainer = root;
root.nextContainer = root;
this.operator = operator;
}
@Override
public Container<T> firstContainer() {
return root.nextContainer;
}
@Override
public Container<T> sentinel() {
return root;
}
@Override
public <R> R performActionAt(int offset, LocationAction<T, R> locationAction) {
Node<T, V> node = root;
while (node != null) {
if (offset < node.offset) {
node = node.leftChild;
} else {
offset -= node.offset;
if (offset < node.size) {
return locationAction.performAction(node, offset);
}
offset -= node.size;
node = node.rightChild;
}
}
throw new IndexOutOfBoundsException("Invalid offest: " + offset + ", size: " + size());
}
@Override
public int size() {
return root.offset;
}
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
private Node<T, V> nextContainer = root.nextContainer;
public boolean hasNext() {
return nextContainer != root;
}
public T next() {
T returnValue = nextContainer.value;
nextContainer = nextContainer.nextContainer;
return returnValue;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Evaluate the offset list using the associative operator provided when it
* was constructed.
*
* @return The result of the evaluation.
*/
public V evaluate() {
if (operator == null) {
throw new IllegalStateException("No associative operator was provided.");
}
if (root.leftChild == null) {
return null;
}
return evaluate(root.leftChild);
}
private V evaluate(Node<T, V> node) {
if (node.subtreeComputation != null) {
return node.subtreeComputation;
}
V value = operator.extract(node.value);
if (node.leftChild != null) {
value = operator.operate(evaluate(node.leftChild), value);
}
if (node.rightChild != null) {
value = operator.operate(value, evaluate(node.rightChild));
}
node.subtreeComputation = value;
return value;
}
/**
* Gets the height of the given subtree.
*
* @param node The root of the subtree.
* @return The height of the subtree. The leaf node is a height of 0.
*/
private static <T, V> int getHeight(Node<T, V> node) {
return (node == null) ? -1 : node.height;
}
}