/**
* Replication Benchmarker
* https://github.com/score-team/replication-benchmarker/ Copyright (C) 2013
* LORIA / Inria / SCORE Team
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package jbenchmarker.treedocReplace;
import jbenchmarker.treedoc.*;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import jbenchmarker.treedoc.TreedocIdentifier.ComponentScanner;
import jbenchmarker.treedoc.TreedocIdentifier.EdgeDirection;
import jbenchmarker.treedoc.TreedocIdentifier.Recorder;
/**
* A node of of Treedoc binary-like tree. Each node have (optionally) left and
* right children. The infix traversal order specifies document content.
* <p>
* Thread-hostile.
*
* @author mzawirski
*/
public class TreedocNode<T> implements Serializable {
// TODO: export as a setting
static final boolean USE_DISAMBIGUATORS_TRICK = true;
protected static int binarySearchByTag(final EdgeDirection direction,
final TreedocNode[] nodes, UniqueTag tag) {
if (nodes == null) {
return -1;
}
if (tag == null) {
tag = getMajorNodeTag(direction);
}
// Based on code from Arrays.binarySearch, since rewriting binary search
// differently does not make sense. I do not expect copyright trobules.
int low = 0;
int high = nodes.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
UniqueTag midTag = nodes[mid].uniqueTag;
if (midTag == null) {
midTag = getMajorNodeTag(direction);
}
int cmp = midTag.compareTo(tag);
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
return mid;
}
}
return -(low + 1);
}
protected static UniqueTag getMajorNodeTag(final EdgeDirection direction) {
return direction == EdgeDirection.RIGHT ? UniqueTag.MAX : UniqueTag.MIN;
}
protected static void getNodesContent(final TreedocNode nodes[],
final StringBuilder contentBuffer) {
if (nodes == null) {
return;
}
for (final TreedocNode child : nodes) {
if (child.getSubtreeSize() > 0) {
child.getSubtreeContent(contentBuffer);
}
}
}
protected static int getNodesNumber(final TreedocNode nodes[]) {
int result = 0;
if (nodes != null) {
for (final TreedocNode child : nodes) {
result += child.getNodesNumber();
}
}
return result;
}
protected UniqueTag uniqueTag;
/**
* Node content, valid only if !tombstone.
*/
protected T content;
protected int subtreeSize;
protected boolean tombstone;
protected TreedocNode leftChildren[];
protected TreedocNode rightChildren[];
/**
* Creates non-empty node.
*/
protected TreedocNode(final UniqueTag uniqueTag, final T content) {
this.uniqueTag = uniqueTag;
this.content = content;
this.subtreeSize = 1;
}
/**
* Creates tombstone node.
*/
protected TreedocNode(final UniqueTag uniqueTag) {
this.uniqueTag = uniqueTag;
this.tombstone = true;
}
protected int getSubtreeSize() {
return subtreeSize;
}
protected void getSubtreeContent(final StringBuilder contentBuffer) {
final int origBufferSize = contentBuffer.length();
getNodesContent(leftChildren, contentBuffer);
if (!tombstone) {
contentBuffer.append(content);
}
getNodesContent(rightChildren, contentBuffer);
// if (contentBuffer.length() - origBufferSize != getSubtreeSize()) {
// throw new IllegalStateException(
// "corrupted state, size of a content does not match internal size counter");
// }
}
protected int getNodesNumber() {
return getNodesNumber(leftChildren) + 1 + getNodesNumber(rightChildren);
}
protected boolean isRoot() {
return false;
}
/**
* Finds an deletes a node indicated by id.
*
* @param id
* @return true if node was still there.
*/
protected boolean findAndDeleteNode(
final Iterator<ComponentScanner> idIterator) {
if (!idIterator.hasNext()) {
final boolean wasTombstone = tombstone;
if (!wasTombstone) {
subtreeSize--;
}
tombstone = true;
return !wasTombstone;
} else {
final ComponentScanner component = idIterator.next();
final EdgeDirection direction = component.getDirection();
final UniqueTag tag = component.getTag();
final TreedocNode children[] = getChildren(direction);
final int childIndex = binarySearchByTag(direction, children, tag);
if (childIndex >= 0) {
final TreedocNode child = children[childIndex];
if (child.findAndDeleteNode(idIterator)) {
subtreeSize--;
// Discard a whole subtree, as close to the root as
// possible.
if (child.subtreeSize == 0 && (isRoot() || subtreeSize > 0)) {
removeEmptyChild(direction, childIndex);
}
return true;
}
}
// Else: subtree already does not exist.
return false;
}
}
protected void findAndInsertNode(
final Iterator<ComponentScanner> idIterator, final List<T> content) {
subtreeSize += content.size();
if (!idIterator.hasNext()) {
// Fill in the last subtree on the path with the content.
createBalancedSubtreeOfContent(content, 0, content.size());
} else {
final TreedocIdentifier.ComponentScanner component = idIterator
.next();
final EdgeDirection direction = component.getDirection();
final UniqueTag tag = component.getTag();
final TreedocNode children[] = getChildren(direction);
int childIndex = binarySearchByTag(direction, children, tag);
final TreedocNode child;
if (childIndex >= 0) {
child = children[childIndex];
} else {
// Create node on the path if necessary.
child = new TreedocNode(tag);
TreedocNode newChildren[];
if (children == null) {
// Sequential case - single child.
newChildren = new TreedocNode[1];
newChildren[0] = child;
} else {
// Concurrent case - multiple children.
newChildren = new TreedocNode[children.length + 1];
childIndex = (childIndex + 1) * -1;
System.arraycopy(children, 0, newChildren, 0, childIndex);
newChildren[childIndex] = child;
System.arraycopy(children, childIndex, newChildren,
childIndex + 1, children.length - childIndex);
}
setChildren(direction, newChildren);
}
child.findAndInsertNode(idIterator, content);
}
}
/**
* Creates a balanced tree of a given length containing a content. Assumes
* that this node is empty.
*
* @param content content for the subtree.
* @param begin first index of content to use.
* @param length size of content to create.
*/
protected void createBalancedSubtreeOfContent(final List<T> content,
final int begin, final int length) {
// Invariant: leftSubtree + rightSubtree + 1 = length
final int leftSubtree = (length - 1) / 2;
final int rightSubtree = length - 1 - leftSubtree;
if (leftSubtree > 0) {
final TreedocNode leftChild = new TreedocNode(uniqueTag);
final TreedocNode[] leftChildren = new TreedocNode[1];
leftChildren[0] = leftChild;
assertHasNoChildren(this.leftChildren);
setChildren(EdgeDirection.LEFT, leftChildren);
leftChild.createBalancedSubtreeOfContent(content, begin,
leftSubtree);
}
this.content = content.get(begin + leftSubtree);
this.tombstone = false;
if (subtreeSize != 0 && subtreeSize != length) {
throw new IllegalStateException(
"trying to create a balanced subtree within the node that already contains "
+ subtreeSize + " nodes");
}
this.subtreeSize = length;
if (rightSubtree > 0) {
final TreedocNode rightChild = new TreedocNode(uniqueTag);
final TreedocNode[] rightChildren = new TreedocNode[1];
rightChildren[0] = rightChild;
assertHasNoChildren(this.rightChildren);
setChildren(EdgeDirection.RIGHT, rightChildren);
rightChild.createBalancedSubtreeOfContent(content, begin
+ leftSubtree + 1, rightSubtree);
}
}
private void assertHasNoChildren(final TreedocNode children[]) {
if (children != null && children.length > 0) {
throw new IllegalStateException(
"trying to create a balance subtree within the node that already contains some children");
}
}
protected TreedocNode[] getChildren(final EdgeDirection direction) {
return direction == EdgeDirection.LEFT ? leftChildren : rightChildren;
}
protected void setChildren(final EdgeDirection direction,
final TreedocNode[] newChildren) {
if (direction == EdgeDirection.LEFT) {
leftChildren = newChildren;
} else {
rightChildren = newChildren;
}
}
protected void removeEmptyChild(final EdgeDirection direction,
final int childIndex) {
final TreedocNode children[] = getChildren(direction);
final TreedocNode newChildren[];
if (children.length == 1) {
newChildren = null;
} else {
newChildren = new TreedocNode[children.length - 1];
System.arraycopy(children, 0, newChildren, 0, childIndex);
System.arraycopy(children, childIndex + 1, newChildren, childIndex,
children.length - childIndex - 1);
}
setChildren(direction, newChildren);
}
protected TreedocNode findNthContentAndAlterSize(final DecreasingCounter n,
final Recorder idRecorder, final int sizeDelta) {
subtreeSize += sizeDelta;
final TreedocNode leftDescendant = findNthContentInChildrenAndAlterSize(
EdgeDirection.LEFT, n, idRecorder, sizeDelta);
if (leftDescendant != null) {
return leftDescendant;
}
if (!tombstone) {
n.decrement(1);
}
if (n.get() == 0) {
return this;
}
final TreedocNode rightDescendant = findNthContentInChildrenAndAlterSize(
EdgeDirection.RIGHT, n, idRecorder, sizeDelta);
if (rightDescendant == null) {
throw new IllegalStateException(
"Could not find a node within a subtree - corrupted metadata!");
}
return rightDescendant;
}
protected TreedocNode findNthContentInChildrenAndAlterSize(
final EdgeDirection direction, final DecreasingCounter n,
final Recorder idRecorder, final int sizeDelta) {
final TreedocNode children[] = getChildren(direction);
if (children == null) {
return null;
}
for (int childIndex = 0; childIndex < children.length; childIndex++) {
final TreedocNode child = children[childIndex];
final int childSize = child.getSubtreeSize();
if (childSize == 0) {
continue;
}
if (n.get() <= childSize) {
// TODO: the fact this exact condition is here is a hack.
if (USE_DISAMBIGUATORS_TRICK) {
if (sizeDelta > 0 && direction == EdgeDirection.RIGHT
&& childSize == n.get()
&& childIndex == children.length - 1
&& children[childIndex].uniqueTag != null) {
final TreedocNode newChildren[] = new TreedocNode[children.length + 1];
System.arraycopy(children, 0, newChildren, 0,
children.length);
final TreedocNode maxTagNode = new TreedocNode(null);
idRecorder.recordEdge(direction, maxTagNode.uniqueTag);
maxTagNode.subtreeSize = sizeDelta;
newChildren[children.length] = maxTagNode;
setChildren(direction, newChildren);
return newChildren[children.length];
}
}
idRecorder.recordEdge(direction, child.uniqueTag);
final TreedocNode foundNode = child.findNthContentAndAlterSize(
n, idRecorder, sizeDelta);
// Discard a whole subtree, as close to the root as possible.
if (child.subtreeSize == 0 && (isRoot() || subtreeSize > 0)) {
removeEmptyChild(direction, childIndex);
}
return foundNode;
} else {
n.decrement(childSize);
}
}
return null;
}
protected TreedocNode insertAfter(final TreedocNode newNode,
final Recorder idRecorder) {
if (rightChildren == null) {
rightChildren = new TreedocNode[1];
rightChildren[0] = newNode;
idRecorder.recordEdge(EdgeDirection.RIGHT, newNode.uniqueTag);
} else {
final TreedocNode firstChild = rightChildren[0];
idRecorder.recordEdge(EdgeDirection.RIGHT, firstChild.uniqueTag);
// TODO: An alternative here is to handle the case
// (node.uniqueTag.compareTo(rightChildren[0].uniqueTag) < 0)
// differently.
firstChild.subtreeSize += newNode.subtreeSize;
firstChild.insertBefore(newNode, idRecorder);
}
return newNode;
}
protected void insertBefore(final TreedocNode newNode,
final Recorder idRecorder) {
if (leftChildren == null) {
leftChildren = new TreedocNode[1];
leftChildren[0] = newNode;
idRecorder.recordEdge(EdgeDirection.LEFT, newNode.uniqueTag);
} else {
final TreedocNode firstChild = leftChildren[0];
idRecorder.recordEdge(EdgeDirection.LEFT, firstChild.uniqueTag);
// TODO: An alternative here is to handle the case
// (node.uniqueTag.compareTo(leftChildren[0].uniqueTag) < 0)
// differently.
firstChild.subtreeSize += newNode.subtreeSize;
firstChild.insertBefore(newNode, idRecorder);
}
}
@Override
public String toString() {
final StringBuilder buf = new StringBuilder(getSubtreeSize());
getNodesContent(leftChildren, buf);
buf.append('|');
buf.append(tombstone ? '!' : content);
buf.append('|');
getNodesContent(rightChildren, buf);
buf.append("|size=");
buf.append(getSubtreeSize());
return buf.toString();
}
/**
* An integer counter object. A work-around for Java, whic does not support
* returning two objects (a pretty handy feature for recursively traversing
* the tree).
*/
protected static class DecreasingCounter {
private int value;
public DecreasingCounter(final int value) {
this.value = value;
}
public int get() {
return value;
}
public void decrement(final int delta) {
value -= delta;
}
}
}