/******************************************************************************* * Copyright (c) 2000, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.core.internal.dtree; import org.eclipse.core.internal.utils.Messages; import org.eclipse.core.internal.utils.StringPool; import org.eclipse.core.runtime.*; /** * Externally, a <code>DeltaDataTree</code> appears to have the same content as * a standard data tree. Internally, the delta tree may be complete, or it may * just indicate the changes between itself and its parent. * * <p>Nodes that exist in the parent but do not exist in the delta, are represented * as instances of <code>DeletedNode</code>. Nodes that are identical in the parent * and the delta, but have differences in their subtrees, are represented as * instances of <code>NoDataDeltaNode</code> in the delta tree. Nodes that differ * between parent and delta are instances of <code>DataDeltaNode</code>. However, * the <code>DataDeltaNode</code> only contains the children whose subtrees differ * between parent and delta. * * A delta tree algebra is used to manipulate sets of delta trees. Given two trees, * one can obtain the delta between the two using the method * <code>forwardDeltaWith(aTree)</code>. Given a tree and a delta, one can assemble * the complete tree that the delta represents using the method <code> * assembleWithForwardDelta</code>. Refer to the public API methods of this class * for further details. */ public class DeltaDataTree extends AbstractDataTree { private AbstractDataTreeNode rootNode; private DeltaDataTree parent; /** * Creates a new empty tree. */ public DeltaDataTree() { this.empty(); } /** * Creates a new tree. * @param rootNode * root node of new tree. */ public DeltaDataTree(AbstractDataTreeNode rootNode) { this.rootNode = rootNode; this.parent = null; } protected DeltaDataTree(AbstractDataTreeNode rootNode, DeltaDataTree parent) { this.rootNode = rootNode; this.parent = parent; } /** * Adds a child to the tree. * * @param parentKey parent for new child. * @param localName name of child. * @param childNode child node. */ protected void addChild(IPath parentKey, String localName, AbstractDataTreeNode childNode) { if (!includes(parentKey)) handleNotFound(parentKey); childNode.setName(localName); this.assembleNode(parentKey, new NoDataDeltaNode(parentKey.lastSegment(), childNode)); } /** * Returns the tree as a backward delta. If the delta is applied to the tree it * will produce its parent. The receiver must have a forward * delta representation. I.e.: Call the receiver's parent A, * and the receiver B. The receiver's representation is A->B. * Returns the delta A<-B. The result is equivalent to A, but has B as its parent. */ DeltaDataTree asBackwardDelta() { if (getParent() == null) return newEmptyDeltaTree(); return new DeltaDataTree(getRootNode().asBackwardDelta(this, getParent(), rootKey()), this); } /** * This method can only be called on a comparison tree created * using DeltaDataTree.compareWith(). This method flips the orientation * of the given comparison tree, so that additions become removals, * and vice-versa. This method destructively changes the tree * as opposed to making a copy. */ public DeltaDataTree asReverseComparisonTree(IComparator comparator) { /* don't reverse the root node if it's the absolute root (name==null) */ if (rootNode.getName() == null) { AbstractDataTreeNode[] children = rootNode.getChildren(); int nextChild = 0; for (int i = 0; i < children.length; i++) { AbstractDataTreeNode newChild = children[i].asReverseComparisonNode(comparator); if (newChild != null) { children[nextChild++] = newChild; } } if (nextChild < children.length) { AbstractDataTreeNode[] newChildren = new AbstractDataTreeNode[nextChild]; System.arraycopy(children, 0, newChildren, 0, nextChild); rootNode.setChildren(newChildren); } } else { rootNode.asReverseComparisonNode(comparator); } return this; } /** * Replaces a node in the tree with the result of assembling the node * with the given delta node (which represents a forward delta on * the existing node). * * @param key key of the node to replace. * @param deltaNode delta node to use to assemble the new node. */ protected void assembleNode(IPath key, AbstractDataTreeNode deltaNode) { rootNode = rootNode.assembleWith(deltaNode, key, 0); } /** * Assembles the receiver with the given delta tree and answer * the resulting, mutable source tree. The given delta tree must be a * forward delta based on the receiver (i.e. missing information is taken from * the receiver). This operation is used to coalesce delta trees. * * <p>In detail, suppose that c is a forward delta over source tree a. * Let d := a assembleWithForwardDelta: c. * d has the same content as c, and is represented as a delta tree * whose parent is the same as a's parent. * * <p>In general, if c is represented as a chain of deltas of length n, * then d is represented as a chain of length n-1. * * <p>So if a is a complete tree (i.e., has no parent, length=0), then d * will be a complete tree too. * * <p>Corollary: (a assembleWithForwardDelta: (a forwardDeltaWith: b)) = b */ public DeltaDataTree assembleWithForwardDelta(DeltaDataTree deltaTree) { return new DeltaDataTree(getRootNode().assembleWith(deltaTree.getRootNode()), this); } /** * Compares this tree with another tree, starting from the given path. The * given path will be the root node of the returned tree. Both this * tree and other tree must contain the given path. */ protected DeltaDataTree basicCompare(DeltaDataTree other, IComparator comparator, IPath path) { DeltaDataTree newTree; if (this == other) { newTree = new DeltaDataTree(); newTree.setData(Path.ROOT, new NodeComparison(null, null, 0, 0)); } else if (other.hasAncestor(this)) { AbstractDataTreeNode assembled = other.searchNodeAt(path); DeltaDataTree tree = other; /* Iterate through the receiver's ancestors until the receiver is reached */ while ((tree = tree.getParent()) != this) { //ancestor may not contain the given path AbstractDataTreeNode treeNode = tree.searchNodeAt(path); if (treeNode != null) { assembled = treeNode.assembleWith(assembled); } } AbstractDataTreeNode comparedRoot = assembled.compareWithParent(path, this, comparator); newTree = new DeltaDataTree(comparedRoot); } else if (this.hasAncestor(other)) { AbstractDataTreeNode assembled = this.asBackwardDelta().searchNodeAt(path); DeltaDataTree tree = this; /* Iterate through the receiver's ancestors until the other tree is reached */ while ((tree = tree.getParent()) != other) { assembled = assembled.assembleWith(tree.asBackwardDelta().searchNodeAt(path)); } AbstractDataTreeNode comparedRoot = assembled.compareWithParent(path, this, comparator); newTree = new DeltaDataTree(comparedRoot); } else { //revert to naive comparison DataTreeNode thisCompleteRoot = (DataTreeNode) this.copyCompleteSubtree(path); DataTreeNode otherCompleteRoot = (DataTreeNode) other.copyCompleteSubtree(path); AbstractDataTreeNode comparedRoot = thisCompleteRoot.compareWith(otherCompleteRoot, comparator); newTree = new DeltaDataTree(comparedRoot); } newTree.immutable(); return newTree; } /** * Collapses this tree so that the given ancestor becomes its * immediate parent. Afterwards, this tree will still have exactly the * same contents, but its internal structure will be compressed. * * <p> This operation should be used to collapse chains of * delta trees that don't contain interesting intermediate states. * * <p>This is a destructive operation, since it modifies the structure of this * tree instance. This tree must be immutable at the start of this operation, * and will be immutable afterwards. * @return this tree. */ public DeltaDataTree collapseTo(DeltaDataTree collapseTo, IComparator comparator) { if (this == collapseTo || getParent() == collapseTo) { //already collapsed return this; } //collapse my tree to be a forward delta of the parent's tree. //c will have the same content as this tree, but its parent will be "parent". DeltaDataTree c = collapseTo.forwardDeltaWith(this, comparator); //update my internal root node and parent pointers. this.parent = collapseTo; this.rootNode = c.rootNode; return this; } /** * Returns a DeltaDataTree that describes the differences between * this tree and "other" tree. Each node of the returned tree * will contain a NodeComparison object that describes the differences * between the two trees. */ public DeltaDataTree compareWith(DeltaDataTree other, IComparator comparator) { DeltaDataTree newTree; if (this == other) { newTree = new DeltaDataTree(); newTree.setData(Path.ROOT, new NodeComparison(null, null, 0, 0)); } else if (other.hasAncestor(this)) { AbstractDataTreeNode assembled = other.getRootNode(); DeltaDataTree tree = other; /* Iterate through the receiver's ancestors until the receiver is reached */ while ((tree = tree.getParent()) != this) { assembled = tree.getRootNode().assembleWith(assembled); } AbstractDataTreeNode comparedRoot = assembled.compareWithParent(rootKey(), this, comparator); newTree = new DeltaDataTree(comparedRoot); } else if (this.hasAncestor(other)) { AbstractDataTreeNode assembled = this.asBackwardDelta().getRootNode(); DeltaDataTree tree = this; /* Iterate through the receiver's ancestors until the other tree is reached */ while ((tree = tree.getParent()) != other) { assembled = assembled.assembleWith(tree.asBackwardDelta().getRootNode()); } AbstractDataTreeNode comparedRoot = assembled.compareWithParent(rootKey(), this, comparator); newTree = new DeltaDataTree(comparedRoot); } else { //revert to naive comparison if trees have no common ancestry DataTreeNode thisCompleteRoot = (DataTreeNode) this.copyCompleteSubtree(rootKey()); DataTreeNode otherCompleteRoot = (DataTreeNode) other.copyCompleteSubtree(rootKey()); AbstractDataTreeNode comparedRoot = thisCompleteRoot.compareWith(otherCompleteRoot, comparator); newTree = new DeltaDataTree(comparedRoot); } newTree.immutable(); return newTree; } /** * Compares this tree with another tree, starting from the given path. The * given path will be the root node of the returned tree. */ public DeltaDataTree compareWith(DeltaDataTree other, IComparator comparator, IPath path) { /* need to figure out if trees really contain the given path */ if (this.includes(path)) { if (other.includes(path)) return basicCompare(other, comparator, path); /* only exists in this tree */ return new DeltaDataTree(AbstractDataTreeNode.convertToRemovedComparisonNode(this.copyCompleteSubtree(path), comparator.compare(this.getData(path), null))); } if (other.includes(path)) /* only exists in other tree */ return new DeltaDataTree(AbstractDataTreeNode.convertToAddedComparisonNode(other.copyCompleteSubtree(path), comparator.compare(null, other.getData(path)))); /* doesn't exist in either tree */ return DeltaDataTree.createEmptyDelta(); } /** * Returns a copy of the tree which shares its instance variables. */ protected AbstractDataTree copy() { return new DeltaDataTree(rootNode, parent); } /** * Returns a complete node containing the contents of a subtree of the tree. * * @param key * key of subtree to copy */ public AbstractDataTreeNode copyCompleteSubtree(IPath key) { AbstractDataTreeNode node = searchNodeAt(key); if (node == null) { handleNotFound(key); return null; } if (node.isDelta()) return naiveCopyCompleteSubtree(key); //copy the node in case the user wants to hammer the subtree name return node.copy(); } /** * @see AbstractDataTree#createChild(IPath, String) */ public void createChild(IPath parentKey, String localName) { createChild(parentKey, localName, null); } /** * @see AbstractDataTree#createChild(IPath, String, Object) */ public void createChild(IPath parentKey, String localName, Object data) { if (isImmutable()) handleImmutableTree(); addChild(parentKey, localName, new DataTreeNode(localName, data)); } /** * Returns a delta data tree that represents an empty delta. * (i.e. it represents a delta on another (unspecified) tree, * but introduces no changes). */ static DeltaDataTree createEmptyDelta() { DeltaDataTree newTree = new DeltaDataTree(); newTree.emptyDelta(); return newTree; } /** * Creates and returns an instance of the receiver. * @see AbstractDataTree#createInstance() */ protected AbstractDataTree createInstance() { return new DeltaDataTree(); } /** * @see AbstractDataTree#createSubtree(IPath, AbstractDataTreeNode) */ public void createSubtree(IPath key, AbstractDataTreeNode node) { if (isImmutable()) handleImmutableTree(); if (key.isRoot()) { setParent(null); setRootNode(node); } else { addChild(key.removeLastSegments(1), key.lastSegment(), node); } } /** * @see AbstractDataTree#deleteChild(IPath, String) */ public void deleteChild(IPath parentKey, String localName) { if (isImmutable()) handleImmutableTree(); /* If the child does not exist */ IPath childKey = parentKey.append(localName); if (!includes(childKey)) handleNotFound(childKey); assembleNode(parentKey, new NoDataDeltaNode(parentKey.lastSegment(), new DeletedNode(localName))); } /** * Initializes the receiver so that it is a complete, empty tree. * @see AbstractDataTree#empty() */ public void empty() { rootNode = new DataTreeNode(null, null); parent = null; } /** * Initializes the receiver so that it represents an empty delta. * (i.e. it represents a delta on another (unspecified) tree, * it introduces no changes). The parent is left unchanged. */ void emptyDelta() { rootNode = new NoDataDeltaNode(null); } /** * Returns a node of the tree if it is present, otherwise returns null * * @param key * key of node to find */ public AbstractDataTreeNode findNodeAt(IPath key) { AbstractDataTreeNode node = rootNode; int segmentCount = key.segmentCount(); for (int i = 0; i < segmentCount; i++) { node = node.childAtOrNull(key.segment(i)); if (node == null) return null; } return node; } /** * Returns a forward delta between the receiver and the given source tree, * using the given comparer to compare data objects. * The result describes the changes which, if assembled with the receiver, * will produce the given source tree. * In more detail, let c = a.forwardDeltaWith(b). * c has the same content as b, but is represented as a delta tree with a as the parent. * Also, c is immutable. * * There is no requirement that a and b be related, although it is usually more * efficient if they are. The node keys are used as the basis of correlation * between trees. * * Note that if b is already represented as a delta over a, * then c will have the same internal structure as b. * Thus the very common case of previous forwardDeltaWith: current * is actually very fast when current is a modification of previous. * * @param sourceTree second delta tree to create a delta between * @param comparer the comparer used to compare data objects * @return the new delta */ public DeltaDataTree forwardDeltaWith(DeltaDataTree sourceTree, IComparator comparer) { DeltaDataTree newTree; if (this == sourceTree) { newTree = this.newEmptyDeltaTree(); } else if (sourceTree.hasAncestor(this)) { AbstractDataTreeNode assembled = sourceTree.getRootNode(); DeltaDataTree treeParent = sourceTree; /* Iterate through the sourceTree's ancestors until the receiver is reached */ while ((treeParent = treeParent.getParent()) != this) { assembled = treeParent.getRootNode().assembleWith(assembled); } newTree = new DeltaDataTree(assembled, this); newTree.simplify(comparer); } else if (this.hasAncestor(sourceTree)) { //create the delta backwards and then reverse it newTree = sourceTree.forwardDeltaWith(this, comparer); newTree = newTree.asBackwardDelta(); } else { DataTreeNode thisCompleteRoot = (DataTreeNode) this.copyCompleteSubtree(rootKey()); DataTreeNode sourceTreeCompleteRoot = (DataTreeNode) sourceTree.copyCompleteSubtree(rootKey()); AbstractDataTreeNode deltaRoot = thisCompleteRoot.forwardDeltaWith(sourceTreeCompleteRoot, comparer); newTree = new DeltaDataTree(deltaRoot, this); } newTree.immutable(); return newTree; } /** * @see AbstractDataTree#getChildCount(IPath) */ public int getChildCount(IPath parentKey) { return getChildNodes(parentKey).length; } /** * Returns the child nodes of a node in the tree. */ protected AbstractDataTreeNode[] getChildNodes(IPath parentKey) { /* Algorithm: * for each delta in chain (going backwards), * get list of child nodes, if any in delta * assemble with previously seen list, if any * break when complete tree found, * report error if parent is missing or has been deleted */ AbstractDataTreeNode[] childNodes = null; int keyLength = parentKey.segmentCount(); for (DeltaDataTree tree = this; tree != null; tree = tree.parent) { AbstractDataTreeNode node = tree.rootNode; boolean complete = !node.isDelta(); for (int i = 0; i < keyLength; i++) { node = node.childAtOrNull(parentKey.segment(i)); if (node == null) { break; } if (!node.isDelta()) { complete = true; } } if (node != null) { if (node.isDeleted()) { break; } if (childNodes == null) { childNodes = node.children; } else { // Be sure to assemble(old, new) rather than (new, old). // Keep deleted nodes if we haven't encountered the complete node yet. childNodes = AbstractDataTreeNode.assembleWith(node.children, childNodes, !complete); } } if (complete) { if (childNodes != null) { return childNodes; } // Not found, but complete node encountered, so should not check parent tree. break; } } if (childNodes != null) { // Some deltas carry info about children, but there is // no complete node against which they describe deltas. Assert.isTrue(false, Messages.dtree_malformedTree); } // Node is missing or has been deleted. handleNotFound(parentKey); return null;//should not get here } /** * @see AbstractDataTree#getChildren(IPath) */ public IPath[] getChildren(IPath parentKey) { AbstractDataTreeNode[] childNodes = getChildNodes(parentKey); int len = childNodes.length; if (len == 0) return NO_CHILDREN; IPath[] answer = new IPath[len]; for (int i = 0; i < len; ++i) answer[i] = parentKey.append(childNodes[i].name); return answer; } /** * Returns the data at a node of the tree. * * @param key * key of node for which to return data. */ public Object getData(IPath key) { /* Algorithm: * for each delta in chain (going backwards), * get node, if any in delta * if it carries data, return it * break when complete tree found * report error if node is missing or has been deleted */ int keyLength = key.segmentCount(); for (DeltaDataTree tree = this; tree != null; tree = tree.parent) { AbstractDataTreeNode node = tree.rootNode; boolean complete = !node.isDelta(); for (int i = 0; i < keyLength; i++) { node = node.childAtOrNull(key.segment(i)); if (node == null) { break; } if (!node.isDelta()) { complete = true; } } if (node != null) { if (node.hasData()) { return node.getData(); } else if (node.isDeleted()) { break; } } if (complete) { // Not found, but complete node encountered, so should not check parent tree. break; } } handleNotFound(key); return null; //can't get here } /** * @see AbstractDataTree#getNameOfChild(IPath, int) */ public String getNameOfChild(IPath parentKey, int index) { AbstractDataTreeNode[] childNodes = getChildNodes(parentKey); return childNodes[index].name; } /** * Returns the local names for the children of a node of the tree. * * @see AbstractDataTree#getNamesOfChildren(IPath) */ public String[] getNamesOfChildren(IPath parentKey) { AbstractDataTreeNode[] childNodes = getChildNodes(parentKey); int len = childNodes.length; String[] namesOfChildren = new String[len]; for (int i = 0; i < len; ++i) namesOfChildren[i] = childNodes[i].name; return namesOfChildren; } /** * Returns the parent of the tree. */ public DeltaDataTree getParent() { return parent; } /** * Returns the root node of the tree. */ protected AbstractDataTreeNode getRootNode() { return rootNode; } /** * Returns true if the receiver's parent has the specified ancestor * * @param ancestor the ancestor in question */ protected boolean hasAncestor(DeltaDataTree ancestor) { DeltaDataTree myParent = this; while ((myParent = myParent.getParent()) != null) { if (myParent == ancestor) { return true; } } return false; } /** * Returns true if the receiver includes a node with * the given key, false otherwise. */ public boolean includes(IPath key) { return searchNodeAt(key) != null; } public boolean isEmptyDelta() { return rootNode.getChildren().length == 0; } /** * Returns an object containing: * - the node key * - a flag indicating whether the specified node was found * - the data for the node, if it was found * * @param key key of node for which we want to retrieve data. */ public DataTreeLookup lookup(IPath key) { int keyLength = key.segmentCount(); for (DeltaDataTree tree = this; tree != null; tree = tree.parent) { AbstractDataTreeNode node = tree.rootNode; boolean complete = !node.isDelta(); for (int i = 0; i < keyLength; i++) { node = node.childAtOrNull(key.segment(i)); if (node == null) { break; } complete |= !node.isDelta(); } if (node != null) { if (node.hasData()) { return DataTreeLookup.newLookup(key, true, node.getData(), tree == this); } else if (node.isDeleted()) { break; } } if (complete) { // Not found, but complete node encountered, so should not check parent tree. break; } } return DataTreeLookup.newLookup(key, false, null); } /** * Returns an object containing: * - the node key * - a flag indicating whether the specified node was found * - the data for the node, if it was found * * This is a case-insensitive variant of the <code>lookup</code> * method. * * @param key key of node for which we want to retrieve data. */ public DataTreeLookup lookupIgnoreCase(IPath key) { int keyLength = key.segmentCount(); for (DeltaDataTree tree = this; tree != null; tree = tree.parent) { AbstractDataTreeNode node = tree.rootNode; boolean complete = !node.isDelta(); for (int i = 0; i < keyLength; i++) { node = node.childAtIgnoreCase(key.segment(i)); if (node == null) { break; } complete |= !node.isDelta(); } if (node != null) { if (node.hasData()) { return DataTreeLookup.newLookup(key, true, node.getData(), tree == this); } else if (node.isDeleted()) { break; } } if (complete) { // Not found, but complete node encountered, so should not check parent tree. break; } } return DataTreeLookup.newLookup(key, false, null); } /** * Converts this tree's representation to be a complete tree, not a delta. * This disconnects this tree from its parents. * The parent trees are unaffected. */ public void makeComplete() { AbstractDataTreeNode assembled = getRootNode(); DeltaDataTree myParent = getParent(); while (myParent != null) { assembled = myParent.getRootNode().assembleWith(assembled); myParent = myParent.getParent(); } setRootNode(assembled); setParent(null); } /** * Returns a complete node containing the contents of the subtree * rooted at <code>key</code> in the receiver. Uses the public API. * * @param key * key of subtree whose contents we want to copy. */ protected AbstractDataTreeNode naiveCopyCompleteSubtree(IPath key) { String[] childNames = getNamesOfChildren(key); int numChildren = childNames.length; AbstractDataTreeNode[] childNodes; if (numChildren == 0) { childNodes = AbstractDataTreeNode.NO_CHILDREN; } else { childNodes = new AbstractDataTreeNode[numChildren]; /* do for each child */ for (int i = numChildren; --i >= 0;) { childNodes[i] = copyCompleteSubtree(key.append(childNames[i])); } } return new DataTreeNode(key.lastSegment(), getData(key), childNodes); } /** * Returns a new tree which represents an empty, mutable delta on the * receiver. It is not possible to obtain a new delta tree if the receiver is * not immutable, as subsequent changes to the receiver would affect the * resulting delta. */ public DeltaDataTree newEmptyDeltaTree() { if (!isImmutable()) throw new IllegalArgumentException(Messages.dtree_notImmutable); DeltaDataTree newTree = (DeltaDataTree) this.copy(); newTree.setParent(this); newTree.emptyDelta(); return newTree; } /** * Makes the receiver the root tree in the list of trees on which it is based. * The receiver's representation becomes a complete tree, while its parents' * representations become backward deltas based on the receiver. * It is not possible to re-root a source tree that is not immutable, as this * would require that its parents be expressed as deltas on a source tree * which could still change. * * @exception RuntimeException * receiver is not immutable */ public DeltaDataTree reroot() { /* self mutex critical region */ reroot(this); return this; } /** * Makes the given source tree the root tree in the list of trees on which it is based. * The source tree's representation becomes a complete tree, while its parents' * representations become backward deltas based on the source tree. * It is not possible to re-root a source tree that is not immutable, as this * would require that its parents be expressed as deltas on a source tree * which could still change. * * @param sourceTree * source tree to set as the new root * @exception RuntimeException * sourceTree is not immutable */ protected void reroot(DeltaDataTree sourceTree) { if (!sourceTree.isImmutable()) handleImmutableTree(); DeltaDataTree sourceParent = sourceTree.getParent(); if (sourceParent == null) return; this.reroot(sourceParent); DeltaDataTree backwardDelta = sourceTree.asBackwardDelta(); DeltaDataTree complete = sourceParent.assembleWithForwardDelta(sourceTree); sourceTree.setRootNode(complete.getRootNode()); sourceTree.setParent(null); sourceParent.setRootNode(backwardDelta.getRootNode()); sourceParent.setParent(sourceTree); } /** * Returns a complete node containing the contents of a subtree of the tree. * Returns null if the node at this key does not exist. This is a thread-safe * version of copyCompleteSubtree * * @param key key of subtree to copy */ public AbstractDataTreeNode safeCopyCompleteSubtree(IPath key) { AbstractDataTreeNode node = searchNodeAt(key); if (node == null) return null; if (node.isDelta()) return safeNaiveCopyCompleteSubtree(key); //copy the node in case the user wants to hammer the subtree name return node.copy(); } /** * Returns a complete node containing the contents of the subtree * rooted at @key in the receiver. Returns null if this node does not exist in * the tree. This is a thread-safe version of naiveCopyCompleteSubtree * * @param key * key of subtree whose contents we want to copy. */ protected AbstractDataTreeNode safeNaiveCopyCompleteSubtree(IPath key) { try { String[] childNames = getNamesOfChildren(key); int numChildren = childNames.length; AbstractDataTreeNode[] childNodes; if (numChildren == 0) { childNodes = AbstractDataTreeNode.NO_CHILDREN; } else { childNodes = new AbstractDataTreeNode[numChildren]; /* do for each child */ int actualChildCount = 0; for (int i = numChildren; --i >= 0;) { childNodes[i] = safeCopyCompleteSubtree(key.append(childNames[i])); if (childNodes[i] != null) actualChildCount++; } //if there are less actual children due to concurrent deletion, shrink the child array if (actualChildCount < numChildren) { AbstractDataTreeNode[] actualChildNodes = new AbstractDataTreeNode[actualChildCount]; for (int iOld = 0, iNew = 0; iOld < numChildren; iOld++) if (childNodes[iOld] != null) actualChildNodes[iNew++] = childNodes[iOld]; childNodes = actualChildNodes; } } return new DataTreeNode(key.lastSegment(), getData(key), childNodes); } catch (ObjectNotFoundException e) { return null; } } /** * Returns the specified node. Search in the parent if necessary. Return null * if the node is not found or if it has been deleted */ protected AbstractDataTreeNode searchNodeAt(IPath key) { int keyLength = key.segmentCount(); for (DeltaDataTree tree = this; tree != null; tree = tree.parent) { AbstractDataTreeNode node = tree.rootNode; boolean complete = !node.isDelta(); for (int i = 0; i < keyLength; i++) { node = node.childAtOrNull(key.segment(i)); if (node == null) { break; } if (!node.isDelta()) { complete = true; } } if (node != null) { if (node.isDeleted()) break; return node; } if (complete) { // Not found, but complete node encountered, so should not check parent tree. break; } } return null; } /** * @see AbstractDataTree#setData(IPath, Object) */ public void setData(IPath key, Object data) { if (isImmutable()) handleImmutableTree(); if (!includes(key)) handleNotFound(key); assembleNode(key, new DataDeltaNode(key.lastSegment(), data)); } /** * Sets the parent of the tree. */ protected void setParent(DeltaDataTree aTree) { parent = aTree; } /** * Sets the root node of the tree */ void setRootNode(AbstractDataTreeNode aNode) { rootNode = aNode; } /** * Simplifies the receiver: * - replaces any DataDelta nodes with the same data as the parent * with a NoDataDelta node * - removes any empty (leaf NoDataDelta) nodes */ protected void simplify(IComparator comparer) { if (parent == null) return; setRootNode(rootNode.simplifyWithParent(rootKey(), parent, comparer)); } /* (non-Javadoc) * Method declared on IStringPoolParticipant */ public void storeStrings(StringPool set){ AbstractDataTreeNode root = null; for(DeltaDataTree dad = this ; dad != null; dad = dad.getParent()){ root = dad.getRootNode(); if (root != null) root.storeStrings(set); } } }