/******************************************************************************* * 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); } } }