/**
* Copyright 2009 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.document.indexed;
import org.waveprotocol.wave.model.util.CollectionFactory;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.ReadableStringMap;
import org.waveprotocol.wave.model.util.ReadableStringSet;
import org.waveprotocol.wave.model.util.StringMap;
import org.waveprotocol.wave.model.util.StringSet;
import org.waveprotocol.wave.model.util.ValueUtils;
import java.util.ArrayList;
import java.util.List;
/**
* A tree-based data structure for annotations.
*
* This has a simple random access interface. AnnotationTree
* implements a streaming interface and other support for
* IndexedDocumentImpl on top of this simple interface.
*
* Note: This is not a brute-force implementation.
*
* @param <V> the value type
*
* @author ohler@google.com (Christian Ohler)
*/
// Package-private because only AnnotationTree needs access.
class BasicAnnotationTree<V> {
// The basic ideas behind this data structure:
//
// The underlying document is a sequence of items that we don't know
// much about (except for its length, since our length is the
// same). We maintain a set of key-value pairs (annotations) for
// every item, with the optimization that we often (but not always)
// merge consecutive runs of items with the same annotations into an
// interval.
//
// These intervals form the leaves of a red-black tree. Each
// interior node of the tree thus represents a sequence of two or
// more intervals and stores its total length to allow quick
// navigation by index. Key-value pairs that are common for the
// entire range represented by an interior node are also stored in
// that node, not in the nodes below it. (Moving common key-value
// pairs from siblings to their parent is what we call
// "propagation".) This, together with the fact that we always
// force each item to have a value for every key, allows relatively
// fast "where is the next change of the value of this key" lookups.
//
// Updates are somewhat complicated. Setting an annotation (or
// deleting a range) can lead to up to two intervals splits and/or
// an arbitrary number of propagations and interval merges. Since
// the red-black tree may have to rotate when internal nodes are
// added or removed, and rotations along the path back to the root
// confuse our recursive update algorithms, we use two tricks: When
// we notice that we have to split nodes, we update only part of the
// intervals, allow the tree to rotate, and then update the
// remainder. When we merge intervals, we defer any deletions (and
// thus rotations) until we have completed the entire update.
//
// Newly inserted items will always inherit the annotations from their
// left neighbors; insertions on the left border will have no annotations
// initially. To avoid special cases in the difficult parts of the
// algorithms, we have one sentinel item on the left with no annotations.
// This is where the +1/-1 operations when translating the API methods to
// the internal tree operations come from.
// TODO(ohler): Maybe it would be simpler to define that this item has the
// index -1?
private enum NodeType {
// Leaves are always black.
LEAF_BLACK, INTERNAL_RED, INTERNAL_BLACK;
}
private final CollectionFactory factory = CollectionUtils.getCollectionFactory();
private final StringSet knownKeys;
// I haven't checked recently whether the sentinel still simplifies things.
// It might be redundant now.
private Node sentinel;
private final V oneValue;
private final V anotherValue;
private List<Node> leavesThatHaveBecomeEmpty = new ArrayList<Node>();
private int nextId = 0;
// We give every node a sequential number for debugging.
private int createNodeId() {
return nextId++;
}
private Node newLeaf(int subtreeLength) {
return new Node(NodeType.LEAF_BLACK, subtreeLength, factory.<V>createStringMap());
}
private Node newInternalNode(int subtreeLength) {
return new Node(NodeType.INTERNAL_BLACK, subtreeLength, factory.<V>createStringMap());
}
private Node newInternalNode(int subtreeLength, StringMap<V> localMap) {
return new Node(NodeType.INTERNAL_BLACK, subtreeLength, localMap);
}
// Instances would be more compact (no pointer to outer class) if this were
// static, but we use things like checkState and the sentinel from the outer
// class, so it would be some work to make it static.
private final class Node {
NodeType type;
protected int subtreeLength;
Node parent;
Node left;
Node right;
protected StringMap<V> localMap;
int id;
private Node(NodeType type, int subtreeLength, StringMap<V> localMap) {
this.subtreeLength = subtreeLength;
this.localMap = localMap;
this.type = type;
this.id = createNodeId();
}
// red/unred state
boolean isRed() {
return type == NodeType.INTERNAL_RED;
}
void setRed(boolean flag) {
if (type == NodeType.LEAF_BLACK) {
assert flag == false;
} else {
type = flag ? NodeType.INTERNAL_RED : NodeType.INTERNAL_BLACK;
}
}
boolean isLeaf() {
return type == NodeType.LEAF_BLACK;
}
boolean isRoot() {
return parent == sentinel;
}
void eraseAnnotations(int nodeStart, String key) {
if (localMap.containsKey(key)) {
localMap.remove(key);
return;
}
assert !isLeaf();
left.eraseAnnotations(nodeStart, key);
right.eraseAnnotations(nodeStart + left.subtreeLength, key);
tryToMergeChildren();
}
boolean isLeftChild() {
if (parent.left == this) {
return true;
} else {
assert parent.right == this;
return false;
}
}
Node sibling() {
// doesn't hold when rebalancing a child of the root
//assert this != root();
if (isLeftChild()) {
return parent.right;
} else {
return parent.left;
}
}
void replaceThisNodeWith(Node other) {
assert other.parent == null;
other.parent = parent;
if (isLeftChild()) {
parent.left = other;
} else {
parent.right = other;
}
// Just to be clean.
parent = null;
}
// Should be used only for debugging utility functions, not on normal
// execution paths.
int absoluteFromRelative(int relativeIndex) {
if (this == sentinel) {
return relativeIndex;
}
if (isLeftChild()) {
return parent.absoluteFromRelative(relativeIndex);
} else {
return parent.absoluteFromRelative(relativeIndex + parent.left.subtreeLength);
}
}
@Override
public String toString() {
String rangeString;
try {
rangeString = "" + absoluteFromRelative(0)
+ "+" + subtreeLength
+ "=" + absoluteFromRelative(subtreeLength);
} catch (RuntimeException e) {
rangeString = "<RuntimeException computing range; length=" + subtreeLength + ">";
}
String typeString;
switch (type) {
case INTERNAL_BLACK:
typeString = "internal, black";
break;
case INTERNAL_RED:
typeString = "internal, red";
break;
case LEAF_BLACK:
typeString = "leaf, black";
break;
default:
typeString = "<error: invalid node type " + type + ">";
}
return pathString() + " Node (" + id() + ") "
+ rangeString + " " + typeString
+ (this == sentinel ? " (sentinel)" : this == root() ? " (root)" : "")
+ " " + mapToString(localMap);
}
String pathString() {
if (isRoot()) {
return "#";
}
if (sentinel == this) {
return "S";
}
if (parent == null) {
// Orphan.
return "O";
}
if (this == parent.left) {
return parent.pathString() + "l";
}
if (this == parent.right) {
return parent.pathString() + "r";
}
return "<not a child of parent>";
}
final String id() {
return id + ":" + Integer.toHexString(System.identityHashCode(this));
}
void rebalanceAfterRemoval() {
if (isRoot()) {
return;
}
Node s = sibling();
if (s.isRed()) {
parent.setRed(true);
s.setRed(false);
if (isLeftChild()) {
parent.rotateL();
} else {
parent.rotateR();
}
}
s = sibling();
assert !s.isLeaf();
if ((!parent.isRed())
&& (!s.isRed())
&& !s.left.isRed()
&& !s.right.isRed()) {
s.setRed(true);
parent.rebalanceAfterRemoval();
return;
}
if ((parent.isRed())
&& (!s.isRed())
&& !s.left.isRed()
&& !s.right.isRed()) {
s.setRed(true);
parent.setRed(false);
return;
}
if (isLeftChild()) {
if (!s.isRed()
&& s.left.isRed()
&& !s.right.isRed()) {
s.setRed(true);
s.left.setRed(false);
s.rotateR();
s = sibling();
}
s.setRed(parent.isRed());
parent.setRed(false);
s.right.setRed(false);
parent.rotateL();
} else {
if (!s.isRed()
&& !s.left.isRed()
&& s.right.isRed()) {
s.setRed(true);
s.right.setRed(false);
s.rotateL();
s = sibling();
}
s.setRed(parent.isRed());
parent.setRed(false);
s.left.setRed(false);
parent.rotateR();
}
}
/**
* Throws an exception if the parent-child pointers or subtreeLength entries
* are inconsistent in the subtree rooted at this node.
*/
void checkTreeStructure() {
if (isLeaf()) {
// nothing to do
} else {
checkState(left.parent == this);
checkState(right.parent == this);
left.checkTreeStructure();
right.checkTreeStructure();
if (this != sentinel) {
checkState(subtreeLength == left.subtreeLength + right.subtreeLength,
"subtree lengths inconsistent", this);
}
}
}
/**
* Throws an exception if the subtree rooted at this node is not balanced.
*/
int checkBalancingAndReturnBlackHeight() {
if (isLeaf()) {
assert !isRed();
return 0;
} else {
if (isRed()) {
checkState(!left.isRed(), "left child of red node is red", this);
checkState(!right.isRed(), "right child of red node is red", this);
}
int leftHeight = left.checkBalancingAndReturnBlackHeight();
int rightHeight = right.checkBalancingAndReturnBlackHeight();
checkState(leftHeight == rightHeight, "black height mismatch at " + this + ": "
+ leftHeight + " left, " + rightHeight + " right", this);
return isRed() ? leftHeight : (leftHeight + 1);
}
}
/**
* Checks the following invariants in the subtree rooted at this node and
* throws an exception if any of them is violated:
*
* - no leaf may have a subtreeLength of zero
*
* - siblings must not have the same annotation (it must be propagated
* to the parent)
*
* - two intervals that are siblings must not have identical annotations
* (they must be merged)
*
* - an internal node must not have a child with zero subtreeLength
*/
void checkPropagationAndMerging() {
if (isLeaf()) {
checkState(subtreeLength > 0, "empty Node", this);
} else {
left.checkPropagationAndMerging();
right.checkPropagationAndMerging();
left.localMap.each(new StringMap.ProcV<V>() {
@Override
public void apply(String key, V valueLeft) {
if (right.localMap.containsKey(key)) {
V valueRight = right.localMap.getExisting(key);
if (ValueUtils.equal(valueLeft, valueRight)) {
checkState(false, "left and right have equal annotations "
+ key + "=" + valueLeft, Node.this);
}
}
}
});
if (left.localMap.isEmpty() && right.localMap.isEmpty()) {
checkState(!(left.isLeaf() && right.isLeaf()),
"two leaves not merged", this);
checkState(!(left.subtreeLength == 0), "left is empty", this);
checkState(!(left.subtreeLength == 0), "right is empty", this);
}
}
}
/**
* Throws an exception if any node in the subtree rooted at this node
* has an annotation that is overridden anywhere further up in the tree.
*/
void checkNoStaleKeys() {
if (!isRoot()) {
localMap.each(new StringMap.ProcV<V>() {
@Override
public void apply(String key, V value) {
// The key must not be set in any ancestor.
for (Node ancestor = Node.this; ancestor != root();
ancestor = ancestor.parent) {
checkState(!ancestor.parent.localMap.containsKey(key), "stale key " + key,
Node.this);
}
}
});
}
if (!isLeaf()) {
left.checkNoStaleKeys();
right.checkNoStaleKeys();
}
}
/**
* Throws an exception if any index in the range covered by the subtree
* rooted at this node has no annotation for the given key.
*/
void checkKeyCoverage(String key) {
if (isLeaf()) {
checkState(localMap.containsKey(key), "key " + key + " has no value", this);
} else {
if (localMap.containsKey(key)) {
return;
}
left.checkKeyCoverage(key);
right.checkKeyCoverage(key);
}
}
// return value of -1 means continue
// return value of >0 means restart from top at that absolute position
// return value of 0 is invalid
int setAnnotationForLeaf(int absoluteNodeStart, int start, int end,
String key, V value) {
assert isLeaf();
start = Math.max(start, 0);
end = Math.min(end, subtreeLength);
if (start >= end) {
return -1;
}
assert localMap.containsKey(key);
if (ValueUtils.equal(localMap.getExisting(key), value)) {
return -1;
}
// Entire node?
if (start == 0 && end == subtreeLength) {
eraseAnnotations(absoluteNodeStart, key);
localMap.put(key, value);
if (parent.tryToPropagateFromChildren(key)) {
return absoluteNodeStart + end;
} else {
return -1;
}
}
// Left part?
if (start == 0) {
Node newParent = splitNode(end);
newParent.pushKeyIntoChildren(key);
newParent.left.setAnnotationForLeaf(absoluteNodeStart, start, end, key, value);
newParent.tryToPropagateFromChildren(key);
newParent.rebalanceAfterInsertion();
return absoluteNodeStart + end;
}
// Right part?
if (end == subtreeLength) {
int split = start;
Node newParent = splitNode(split);
newParent.pushKeyIntoChildren(key);
newParent.right.setAnnotationForLeaf(absoluteNodeStart + split,
start - split, end - split, key, value);
newParent.tryToPropagateFromChildren(key);
newParent.rebalanceAfterInsertion();
return absoluteNodeStart + end;
}
// Somewhere in the middle.
V previousValue = localMap.getExisting(key);
int indexOnRight = absoluteNodeStart + subtreeLength;
V valueOnRight = indexOnRight == root().subtreeLength ?
// null is acceptable here because we merely need a value that
// will inhibit upwards propagation of this key from the temporary
// right interval and the interval to the right of it. If we
// are looking at the rightmost interval, there is no interval
// to the right of it that would be a candidate for upwards propagation.
null : getAnnotationRaw(indexOnRight, key);
int split1 = start;
int split2 = end;
// newLeft will retain previous value, temporaryRight will be split in
// two nodes: newMiddle which contains the new value, newRight which contains
// the previous value.
Node newParent1 = splitNode(start);
newParent1.pushKeyIntoChildren(key);
Node newLeft = newParent1.left;
Node temporaryRight = newParent1.right;
temporaryRight.localMap.put(key, differentValue(previousValue, valueOnRight));
// assert that tryToPropagateFromChildren would do nothing
//newParent1.checkPropagationAndMerging();
newParent1.rebalanceAfterInsertion();
temporaryRight.localMap.put(key, previousValue);
Node newParent2 = temporaryRight.splitNode(split2 - split1);
newParent2.pushKeyIntoChildren(key);
Node newMiddle = newParent2.left;
Node newRight = newParent2.right;
assert start - split1 == 0;
assert end - split2 == 0;
assert end - split1 == newMiddle.subtreeLength;
// Even though ranges covered by internal nodes may have shifted during
// rotation, each leaf will still represent the same range, so we can
// still use start, split and end with the same semantics.
newMiddle.setAnnotationForLeaf(absoluteNodeStart + split1, start - split1, end - split1,
key, value);
newParent2.tryToPropagateFromChildren(key);
newParent2.rebalanceAfterInsertion();
return absoluteNodeStart + end;
}
// Replaces this node with a new internal node with two children.
// This node will be disconnected from the tree to be discarded.
Node splitNode(int splitIndex) {
assert isLeaf();
Node newLeft = newLeaf(splitIndex);
Node newRight = newLeaf(subtreeLength - splitIndex);
Node newParent = newInternalNode(subtreeLength, localMap);
newLeft.parent = newParent;
newRight.parent = newParent;
newParent.left = newLeft;
newParent.right = newRight;
newParent.setRed(true);
replaceThisNodeWith(newParent);
// Cannot rebalance here; caller needs a chance to operate on children
// in the state that it expects.
return newParent;
}
void pushKeyIntoChildren(String key) {
assert !isLeaf();
assert localMap.containsKey(key);
V value = localMap.getExisting(key);
left.localMap.put(key, value);
right.localMap.put(key, value);
localMap.remove(key);
}
Node grandparent() {
if (parent == sentinel || parent.parent == sentinel) {
return null;
}
return parent.parent;
}
Node uncle() {
if (parent == sentinel || parent.parent == sentinel) {
return null;
}
if (parent.isLeftChild()) {
return parent.parent.right;
} else {
return parent.parent.left;
}
}
void rebalanceAfterInsertion() {
assert !isLeaf();
if (parent == sentinel) {
setRed(false);
return;
}
if (!parent.isRed()) {
return;
}
if (parent.parent == sentinel) {
parent.setRed(false);
return;
}
Node g = parent.parent;
assert !g.isRed();
{
Node u = parent.sibling();
if (g != sentinel && u.isRed()) {
parent.setRed(false);
u.setRed(false);
g.setRed(true);
g.rebalanceAfterInsertion();
return;
}
}
if (!isLeftChild() && parent.isLeftChild()) {
Node n = parent;
parent.rotateL();
g = this.parent;
this.setRed(false);
g.setRed(true);
assert n.isLeftChild() && this.isLeftChild();
g.rotateR();
return;
} else if (isLeftChild() && !parent.isLeftChild()) {
Node n = parent;
parent.rotateR();
Node p = n.parent;
g = this.parent;
this.setRed(false);
g.setRed(true);
assert !n.isLeftChild() && !p.isLeftChild();
g.rotateL();
return;
}
parent.setRed(false);
g.setRed(true);
if (isLeftChild() && parent.isLeftChild()) {
g.rotateR();
return;
} else {
assert !isLeftChild();
assert !parent.isLeftChild();
g.rotateL();
return;
}
}
void rotateL() {
assert !isLeaf();
// p p
// | |
// a c
// / \ -> / \
// (b) c a (e)
// / \ / \
// d (e) (b) d
Node a = this;
Node p = a.parent;
Node b = a.left;
// We would have no reason to rotate if c was not an internal node.
assert !a.right.isLeaf();
Node c = a.right;
Node d = c.left;
Node e = c.right;
prepareMapsBeforeSingleRotation(a, b, c, d, e);
c.parent = null;
a.replaceThisNodeWith(c);
a.right = d;
c.left = a;
a.parent = c;
c.parent = p;
d.parent = a;
a.subtreeLength -= e.subtreeLength;
c.subtreeLength += b.subtreeLength;
fixupMapsAfterSingleRotation(a, b, c, d, e);
a.tryToMergeChildren();
}
void rotateR() {
assert !isLeaf();
// p p
// | |
// a c
// / \ -> / \
// c (b) (e) a
// / \ / \
// (e) d d (b)
Node a = this;
Node p = a.parent;
Node b = a.right;
// We would have no reason to rotate if c was not an internal node.
assert !a.left.isLeaf();
Node c = a.left;
Node d = c.right;
Node e = c.left;
prepareMapsBeforeSingleRotation(a, b, c, d, e);
c.parent = null;
a.replaceThisNodeWith(c);
a.left = d;
c.right = a;
a.parent = c;
c.parent = p;
d.parent = a;
a.subtreeLength -= e.subtreeLength;
c.subtreeLength += b.subtreeLength;
fixupMapsAfterSingleRotation(a, b, c, d, e);
a.tryToMergeChildren();
}
// Rotate left child to the left, then rotate self to the right.
void rotateLR() {
left.rotateL();
this.rotateR();
}
// Rotate right child to the right, then rotate self to the left.
void rotateRL() {
right.rotateR();
this.rotateL();
}
void tryToMergeChildren() {
assert !isLeaf();
if (left.localMap.isEmpty() && right.localMap.isEmpty()
&& left.isLeaf() && right.isLeaf()) {
int rightLength = right.subtreeLength;
if (rightLength == 0) {
return;
}
if (left.subtreeLength == 0) {
return;
}
left.subtreeLength += rightLength;
right.subtreeLength = 0;
leavesThatHaveBecomeEmpty.add(right);
}
}
boolean tryToPropagateFromChildren(String key) {
assert !isLeaf();
if (left.localMap.containsKey(key) && right.localMap.containsKey(key)) {
V valueLeft = left.localMap.getExisting(key);
if (ValueUtils.equal(valueLeft, right.localMap.getExisting(key))) {
localMap.put(key, valueLeft);
left.localMap.remove(key);
right.localMap.remove(key);
tryToMergeChildren();
parent.tryToPropagateFromChildren(key);
return true;
}
}
return false;
}
void tryToPropagateFromChildren(ReadableStringMap<V> entries) {
assert !isLeaf();
entries.each(new StringMap.ProcV<V>() {
@Override
public void apply(String key, V value) {
tryToPropagateFromChildren(key);
}
});
}
void replaceNodeWithSoleChild(Node child) {
assert !isLeaf();
assert this.left == child || this.right == child;
assert child.parent == this;
assert (child.isLeftChild() && this.right.subtreeLength == 0)
|| (!child.isLeftChild() && this.left.subtreeLength == 0);
// Disconnect other child just to be clean.
if (child.isLeftChild()) {
right.parent = null;
} else {
left.parent = null;
}
child.parent = null;
replaceThisNodeWith(child);
assert child.parent != null;
child.parent.tryToMergeChildren();
if (isRed()) {
// This node was red; black height hasn't changed, nothing to do.
return;
}
if (child.isRed()) {
child.setRed(false);
return;
}
child.rebalanceAfterRemoval();
child.parent.tryToMergeChildren();
}
void tryToCollapse() {
assert !isLeaf();
if (subtreeLength == 0) {
Node originalParent = this.parent;
replaceNodeWithSoleChild(right);
// assert: all leaves in right subtree are in leavesThatHaveBecomeEmpty
return;
}
if (left.subtreeLength == 0) {
right.localMap.putAll(localMap);
Node originalParent = this.parent;
replaceNodeWithSoleChild(right);
return;
}
if (right.subtreeLength == 0) {
left.localMap.putAll(localMap);
Node originalParent = this.parent;
replaceNodeWithSoleChild(left);
return;
}
}
void printForDebugging(StringBuilder out) {
if (isLeaf()) {
out.append(toString() + "\n");
} else {
if (right == null) {
out.append("null right?!\n");
} else {
right.printForDebugging(out);
}
out.append(toString() + "\n");
if (left == null) {
out.append("null left?!\n");
} else {
left.printForDebugging(out);
}
}
}
}
private void prepareMapsBeforeSingleRotation(Node a, Node b, Node c,
Node d, Node e) {
// do nothing
}
/**
* A' = (D union C) intersect B
* B' = B minus (D union C)
* C' = A
* D' = (D union C) minus B
* E' = E union C
*/
private void fixupMapsAfterSingleRotation(Node a, Node b, Node c, Node d, Node e) {
// e.g.
// p p
// | |
// a c
// / \ -> / \
// c (b) (e) a
// / \ / \
// (e) d d (b)
// within each of the following sets, the sets will be disjoint:
// {A, B}, {A, C, D}, {A, C, E}
//
// C' = A
// A' = (D union C) intersect B
// B' = B minus (D union C)
// D' = (D union C) minus B
// E' = E union C
StringMap<V> a0 = a.localMap;
final StringMap<V> b0 = b.localMap;
StringMap<V> c0 = c.localMap;
final StringMap<V> d0 = d.localMap;
final StringMap<V> e0 = e.localMap;
// Strategy:
// create new set for a
// iterate over d
// if in b with same value,
// add to a
// remove from b
// remove from d
// now we have: a = d intersect b, d = d minus b, b = b minus d
// iterate over c
// if in b with same value,
// remove from b
// add to a
// else
// add to d
// add to e
// now we have: a = (d intersect b) union (c intersect (b minus d));
// d = (d minus b) union (c minus (b minus d)); e = e union c, b = (b minus d) minus c
final StringMap<V> a1 = factory.createStringMap();
d0.filter(new StringMap.EntryFilter<V>() {
@Override
public boolean apply(String key, V value) {
if (b0.containsKey(key) && ValueUtils.equal(b0.getExisting(key), value)) {
a1.put(key, value);
b0.remove(key);
return false;
}
return true;
}
});
c0.each(new StringMap.ProcV<V>() {
@Override
public void apply(String key, V value) {
if (b0.containsKey(key) && ValueUtils.equal(b0.getExisting(key), value)) {
b0.remove(key);
a1.put(key, value);
} else {
d0.put(key, value);
}
e0.put(key, value);
}
});
a.localMap = a1;
c.localMap = a0;
}
private Node root() {
return sentinel.left;
}
// TODO(ohler): oneValue and anotherValue are an ugly hack. Perhaps find a way
// to fix the setAnnotation algorithm to not rely on them, or perhaps remove V and
// always store Object so we can generate our own sentinels.
public BasicAnnotationTree(V oneValue, V anotherValue) {
this.knownKeys = factory.createStringSet();
this.oneValue = oneValue;
this.anotherValue = anotherValue;
clear();
}
private void clear() {
// One sentinel item at the start (insertion inherits from the left, so we
// need something on our left to inherit from).
Node root = newLeaf(1);
sentinel = newInternalNode(-1);
sentinel.left = root;
root.parent = sentinel;
sentinel.right = newLeaf(0);
sentinel.right.parent = sentinel;
knownKeys.clear();
}
public int length() {
return root().subtreeLength - 1;
}
/**
* Returns a read-only view of the known key set of this annotation
* tree.
*/
// This is a view because AnnotationTree.knownKeysLive() relies on
// that.
public ReadableStringSet knownKeys() {
return knownKeys;
}
private V getAnnotationRaw(int index, String key) {
Node node = root();
while (true) {
if (node.localMap.containsKey(key)) {
return node.localMap.getExisting(key);
}
assert !node.isLeaf();
int leftLength = node.left.subtreeLength;
if (index < leftLength) {
node = node.left;
} else {
index -= leftLength;
node = node.right;
}
}
}
public V getAnnotation(int index, String key) {
assert 0 <= index;
assert index < length();
if (!knownKeys.contains(key)) {
return null;
}
return getAnnotationRaw(index + 1, key);
}
private void forEachAnnotationAtRaw(int index, ReadableStringMap.ProcV<V> callback) {
Node node = root();
int nodeStart = 0;
while (true) {
node.localMap.each(callback);
if (node.isLeaf()) {
break;
}
int leftLength = node.left.subtreeLength;
if (index < nodeStart + leftLength) {
node = node.left;
} else {
node = node.right;
nodeStart += leftLength;
}
}
}
public void forEachAnnotationAt(int index, ReadableStringMap.ProcV<V> callback) {
forEachAnnotationAtRaw(index + 1, callback);
}
private void collectAllAnnotationsAtRaw(int index, StringMap<V> accu) {
Node node = root();
int nodeStart = 0;
while (true) {
accu.putAll(node.localMap);
if (node.isLeaf()) {
break;
}
int leftLength = node.left.subtreeLength;
if (index < nodeStart + leftLength) {
node = node.left;
} else {
node = node.right;
nodeStart += leftLength;
}
}
}
public void collectAllAnnotationsAt(int index, StringMap<V> accu) {
collectAllAnnotationsAtRaw(index + 1, accu);
}
private void insertRaw(int firstShiftedIndex, int length) {
Node node = root();
int nodeStart = 0;
while (true) {
node.subtreeLength += length;
if (node.isLeaf()) {
return;
}
if (firstShiftedIndex <= nodeStart + node.left.subtreeLength) {
node = node.left;
} else {
nodeStart += node.left.subtreeLength;
node = node.right;
}
}
}
private void deleteRaw(int start, int end) {
Node node = root();
int nodeStart = 0;
outer:
while (true) {
if (end <= nodeStart) {
return;
}
int nodeEnd = nodeStart + node.subtreeLength;
assert start < nodeEnd;
int deletionStart = Math.max(nodeStart, start);
int deletionEnd = Math.min(nodeEnd, end);
int deletionLength = deletionEnd - deletionStart;
assert deletionLength > 0;
node.subtreeLength -= deletionLength;
if (node.isLeaf()) {
end -= deletionLength;
if (node.subtreeLength == 0) {
leavesThatHaveBecomeEmpty.add(node);
}
} else {
if (start < nodeStart + node.left.subtreeLength) {
node = node.left;
} else {
nodeStart += node.left.subtreeLength;
node = node.right;
}
continue outer;
}
// next node
while (true) {
if (node.isLeftChild()) {
nodeStart += node.subtreeLength;
node = node.parent.right;
continue outer;
}
nodeStart -= node.parent.left.subtreeLength;
node = node.parent;
if (node.isRoot()) {
return;
}
}
}
}
// TODO(ohler): Eliminate restarting and implement eraseAnnotationsRaw.
private int setAnnotationRaw(int start, int end, String key, V value) {
Node node = root();
int nodeStart = 0;
outer:
while (true) {
if (end <= nodeStart) {
return -1;
}
if (!(node.localMap.containsKey(key)
&& ValueUtils.equal(node.localMap.getExisting(key), value))) {
int nodeEnd = nodeStart + node.subtreeLength;
// entire node?
if (start <= nodeStart && end >= nodeEnd) {
node.eraseAnnotations(nodeStart, key);
node.localMap.put(key, value);
if (node.parent.tryToPropagateFromChildren(key)) {
return nodeStart + node.subtreeLength;
} else {
// go to next node;
}
} else {
// partial node
if (node.isLeaf()) {
int leafResult = node.setAnnotationForLeaf(nodeStart, start - nodeStart,
end - nodeStart, key, value);
if (leafResult == -1) {
// go to next node
} else {
assert leafResult > 0;
return leafResult;
}
} else {
if (node.localMap.containsKey(key)) {
node.pushKeyIntoChildren(key);
}
if (start < nodeStart + node.left.subtreeLength) {
node = node.left;
} else {
nodeStart += node.left.subtreeLength;
node = node.right;
}
continue outer;
}
}
}
// next node
while (true) {
if (node.isLeftChild()) {
nodeStart += node.subtreeLength;
node = node.parent.right;
continue outer;
}
nodeStart -= node.parent.left.subtreeLength; // law of demeter
node = node.parent;
if (node.isRoot()) {
return -1;
}
}
}
}
public void setAnnotation(int start, int end, String key, V value) {
assert 0 <= start;
assert start <= end;
assert end <= length();
if (start >= end) {
return;
}
if (!knownKeys.contains(key)) {
root().localMap.put(key, null);
knownKeys.add(key);
}
int currentStart = start + 1;
int end1 = end + 1;
do {
currentStart = setAnnotationRaw(currentStart, end1, key, value);
} while (currentStart != -1);
cleanupLeavesThatHaveBecomeEmpty();
// TODO(ohler): change the adapter that implements the streaming
// interface to not call setAnnotation while iterating over the
// known key set. Then we can reenable this. For now, we have
// a separate, less efficient method cleanupKnownKeys() below
// that the adapter invokes explicitly when it's safe.
if (false) {
if (value == null
&& root().localMap.containsKey(key)
&& root().localMap.getExisting(key) == null) {
// Make sure the data structure does not grow without bound even for an
// unbounded key set as long as only a bounded number of keys is in use
// at any time.
root().localMap.remove(key);
knownKeys.remove(key);
}
}
}
public void cleanupKnownKeys() {
knownKeys.filter(new StringSet.StringPredicate() {
@Override
public boolean apply(String key) {
if (root().localMap.containsKey(key)
&& root().localMap.getExisting(key) == null) {
root().localMap.remove(key);
return false;
} else {
return true;
}
}
});
}
// TODO(ohler): Verify this code. It is the bottleneck for these queries.
// Perhaps it's not pruning as much as it should?
private int firstAnnotationChangeRaw(int start, int end, String key, V fromValue) {
Node node = root();
int nodeStart = 0;
outer:
while (true) {
if (nodeStart >= end) {
return -1;
}
if (node.localMap.containsKey(key)) {
V valueHere = node.localMap.getExisting(key);
if (!ValueUtils.equal(valueHere, fromValue)) {
return Math.max(nodeStart, start);
}
if (node.isRoot()) {
return -1;
}
// InternalAnnotationsCursor seems to have similar but simpler code.
while (true) {
if (node.isLeftChild()) {
nodeStart += node.subtreeLength;
node = node.parent.right;
continue outer;
}
nodeStart -= node.parent.left.subtreeLength;
node = node.parent;
if (node.isRoot()) {
return -1;
}
}
}
assert !node.isLeaf();
Node leftNode = node.left;
if (leftNode == null) {
// Log some additional information in the hope that this
// will help track down bug 1816163.
throw new NullPointerException("Unexpected null leftNode:\n"
+ toStringForDebugging());
}
if (start >= nodeStart + leftNode.subtreeLength) {
nodeStart += leftNode.subtreeLength;
node = node.right;
} else {
node = node.left;
}
}
}
private int lastAnnotationChangeRaw(int start, int end, String key, V fromValue) {
Node node = root();
int nodeEnd = node.subtreeLength;
outer:
while (true) {
if (nodeEnd <= start) {
return -1;
}
if (node.localMap.containsKey(key)) {
V valueHere = node.localMap.getExisting(key);
if (!ValueUtils.equal(valueHere, fromValue)) {
return Math.min(nodeEnd, end);
}
if (node.isRoot()) {
return -1;
}
while (true) {
if (!node.isLeftChild()) {
nodeEnd -= node.subtreeLength;
node = node.parent.left;
continue outer;
}
nodeEnd += node.parent.right.subtreeLength;
node = node.parent;
if (node.isRoot()) {
return -1;
}
}
}
assert !node.isLeaf();
Node rightNode = node.right;
if (end <= nodeEnd - rightNode.subtreeLength) {
nodeEnd -= rightNode.subtreeLength;
node = node.left;
} else {
node = node.right;
}
}
}
public int firstAnnotationChange(int start, int end, String key, V fromValue) {
assert 0 <= start;
assert start <= end;
assert end <= length();
if (start >= end) {
return -1;
}
if (!knownKeys.contains(key)) {
if (fromValue == null) {
return -1;
} else {
return start;
}
}
int pos = firstAnnotationChangeRaw(start + 1, end + 1, key, fromValue);
if (pos == -1) {
return -1;
}
assert pos != 0;
return pos - 1;
}
public int lastAnnotationChange(int start, int end, String key, V fromValue) {
assert 0 <= start;
assert start <= end;
assert end <= length();
if (start >= end) {
return -1;
}
if (!knownKeys.contains(key)) {
if (fromValue == null) {
return -1;
} else {
return end;
}
}
int pos = lastAnnotationChangeRaw(start + 1, end + 1, key, fromValue);
if (pos == -1) {
return -1;
}
assert pos != 0;
return pos - 1;
}
private void tryToPropagateFromDyingSubtree(Node child) {
Node parent = child.parent;
while (parent.subtreeLength == 0) {
child = parent;
parent = child.parent;
}
Node sibling = child.sibling();
ReadableStringMap<V> entries = sibling.localMap;
parent.localMap.putAll(entries);
parent.parent.tryToPropagateFromChildren(entries);
// There is some inefficiency here. We shouldn't have to clear any
// map, one node is going to die anyway.
sibling.localMap.clear();
}
// If this cleanup turns out to be expensive, we should be able to
// defer the work and do it incrementally fairly easily. Relaxing
// the invariants accordingly (allowing empty leaves) should be no
// problem. We may even be able to recycle the empty leaves in some
// cases.
private void cleanupLeavesThatHaveBecomeEmpty() {
while (!leavesThatHaveBecomeEmpty.isEmpty()) {
List<Node> mine = leavesThatHaveBecomeEmpty;
leavesThatHaveBecomeEmpty = new ArrayList<Node>();
for (Node leaf : mine) {
assert leaf.subtreeLength == 0;
assert !leaf.isRoot();
tryToPropagateFromDyingSubtree(leaf);
Node originalParent = leaf.parent;
originalParent.tryToCollapse();
}
}
}
public void delete(int start, int end) {
assert 0 <= start;
assert start <= end;
assert end <= length();
if (start >= end) {
return;
}
deleteRaw(start + 1, end + 1);
cleanupLeavesThatHaveBecomeEmpty();
}
public void insert(int firstShiftedIndex, int length) {
assert firstShiftedIndex <= length();
insertRaw(firstShiftedIndex + 1, length);
}
public String toStringForDebugging() {
StringBuilder out = new StringBuilder();
out.append("AnnotationTree, length " + length() + ", sentinel=" + sentinel + ":\n");
root().printForDebugging(out);
// Remove trailing newline so that it can be used nicely with
// println() and does not lead to a redundant empty line when
// used as an exception message.
if (out.charAt(out.length() - 1) == '\n') {
out.setLength(out.length() - 1);
}
return out.toString();
}
private V differentValue(V notThis, V notThat) {
if (!oneValue.equals(notThis) && !oneValue.equals(notThat)) {
return oneValue;
}
if (!anotherValue.equals(notThis) && !anotherValue.equals(notThat)) {
return anotherValue;
}
assert notThis != null && notThat != null;
return null;
}
private void checkState(boolean condition, String description, Node node) {
if (!condition) {
String message = "Tree invariant check failed at node " + node + ": " + description
+ "\n" + toStringForDebugging();
System.err.println("*** " + message);
throw new RuntimeException(message);
}
}
private void checkState(boolean condition) {
if (!condition) {
String message = "Tree invariant check failed\n" + toStringForDebugging();
System.err.println("*** " + message);
throw new RuntimeException(message);
}
}
public void checkSomeInvariants() {
checkState(leavesThatHaveBecomeEmpty.isEmpty());
checkSentinels();
checkTreeStructure();
checkBalancing();
checkPropagationAndMerging();
checkStaleness();
checkKnownKeysSetEverywhere();
}
private void checkTreeStructure() {
checkState(root().parent == sentinel);
root().checkTreeStructure();
}
private void checkBalancing() {
checkState(!root().isRed());
root().checkBalancingAndReturnBlackHeight();
}
private void checkPropagationAndMerging() {
root().checkPropagationAndMerging();
}
private void checkStaleness() {
root().checkNoStaleKeys();
}
private void checkSentinels() {
checkState(!sentinel.isRed());
checkState(sentinel.right.isLeaf());
checkState(sentinel.right.subtreeLength == 0);
checkState(sentinel.localMap.isEmpty());
checkState(sentinel.subtreeLength == -1);
// Check that position 0 exists and has only annotations with the value null.
checkState(root().subtreeLength >= 1);
knownKeys.each(new StringSet.Proc() {
@Override
public void apply(String key) {
checkState(ValueUtils.equal(getAnnotationRaw(0, key), null));
}
});
}
private void checkKnownKeysSetEverywhere() {
knownKeys.each(new StringSet.Proc() {
@Override
public void apply(String key) {
root().checkKeyCoverage(key);
}
});
}
private String mapToString(StringMap<V> map) {
final StringBuilder buf = new StringBuilder("{");
final boolean first[] = new boolean[] { true };
map.each(new StringMap.ProcV<V>() {
@Override
public void apply(String key, V value) {
if (first[0]) {
first[0] = false;
} else {
buf.append(", ");
}
buf.append(key + "=" + value);
}
});
buf.append("}");
return buf.toString();
}
}