blob: b1fe55ae15d871083ee42bdd711939b127dff26b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2014 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.dtree;
import org.eclipse.core.runtime.IPath;
/**
* A <code>DataTree</code> represents the complete information of a source tree.
* The tree contains all information within its branches, and has no relation to any
* other source trees (no parent and no children).
*/
public class DataTree extends AbstractDataTree {
/**
* the root node of the tree
*/
private DataTreeNode rootNode;
/**
* Creates a new empty tree
*/
public DataTree() {
this.empty();
}
/**
* Returns a copy of the node subtree rooted at the given key.
*
* @param key
* Key of subtree to copy
*/
@Override
public AbstractDataTreeNode copyCompleteSubtree(IPath key) {
DataTreeNode node = findNodeAt(key);
if (node == null) {
handleNotFound(key);
}
return copyHierarchy(node);
}
/**
* Returns a deep copy of a node and all its children.
*
* @param node
* Node to be copied.
*/
DataTreeNode copyHierarchy(DataTreeNode node) {
DataTreeNode newNode;
int size = node.size();
if (size == 0) {
newNode = new DataTreeNode(node.getName(), node.getData());
} else {
AbstractDataTreeNode[] children = node.getChildren();
DataTreeNode[] newChildren = new DataTreeNode[size];
for (int i = size; --i >= 0;) {
newChildren[i] = this.copyHierarchy((DataTreeNode) children[i]);
}
newNode = new DataTreeNode(node.getName(), node.getData(), newChildren);
}
return newNode;
}
/**
* Creates a new child in the tree.
* @see AbstractDataTree#createChild(IPath, String)
*/
@Override
public void createChild(IPath parentKey, String localName) {
createChild(parentKey, localName, null);
}
/**
* Creates a new child in the tree.
* @see AbstractDataTree#createChild(IPath, String, Object)
*/
@Override
public void createChild(IPath parentKey, String localName, Object data) {
DataTreeNode node = findNodeAt(parentKey);
if (node == null)
handleNotFound(parentKey);
if (this.isImmutable())
handleImmutableTree();
/* If node already exists, replace */
if (node.includesChild(localName)) {
node.replaceChild(localName, new DataTreeNode(localName, data));
} else {
this.replaceNode(parentKey, node.copyWithNewChild(localName, new DataTreeNode(localName, data)));
}
}
/**
* Creates and returns an instance of the receiver. This is an
* implementation of the factory method creational pattern for allowing
* abstract methods to create instances
*/
@Override
protected AbstractDataTree createInstance() {
return new DataTree();
}
/**
* Creates or replaces a subtree in the tree. The parent node must exist.
*
* @param key
* Key of parent node whose subtree we want to create/replace.
* @param subtree
* New node to insert into tree.
*/
@Override
public void createSubtree(IPath key, AbstractDataTreeNode subtree) {
// Copy it since destructive mods are allowed on the original
// and shouldn't affect this tree.
DataTreeNode newNode = copyHierarchy((DataTreeNode) subtree);
if (this.isImmutable()) {
handleImmutableTree();
}
if (key.isRoot()) {
setRootNode(newNode);
} else {
String localName = key.lastSegment();
newNode.setName(localName); // Another mod, but it's OK we've already copied
IPath parentKey = key.removeLastSegments(1);
DataTreeNode node = findNodeAt(parentKey);
if (node == null) {
handleNotFound(parentKey);
}
/* If node already exists, replace */
if (node.includesChild(localName)) {
node.replaceChild(localName, newNode);
}
this.replaceNode(parentKey, node.copyWithNewChild(localName, newNode));
}
}
/**
* Deletes a child of the tree.
* @see AbstractDataTree#deleteChild(IPath, String)
*/
@Override
public void deleteChild(IPath parentKey, String localName) {
if (this.isImmutable())
handleImmutableTree();
DataTreeNode node = findNodeAt(parentKey);
if (node == null || (!node.includesChild(localName))) {
handleNotFound(node == null ? parentKey : parentKey.append(localName));
} else {
this.replaceNode(parentKey, node.copyWithoutChild(localName));
}
}
/**
* Initializes the receiver.
* @see AbstractDataTree#empty()
*/
@Override
public void empty() {
this.setRootNode(new DataTreeNode(null, null));
}
/**
* Returns the specified node if it is present, otherwise returns null.
*
* @param key
* Key of node to return
*/
public DataTreeNode findNodeAt(IPath key) {
AbstractDataTreeNode node = this.getRootNode();
int keyLength = key.segmentCount();
for (int i = 0; i < keyLength; i++) {
try {
node = node.childAt(key.segment(i));
} catch (ObjectNotFoundException notFound) {
return null;
}
}
return (DataTreeNode) node;
}
/**
* Returns the data at the specified node.
*
* @param key
* Node whose data to return.
*/
@Override
public Object getData(IPath key) {
DataTreeNode node = findNodeAt(key);
if (node == null) {
handleNotFound(key);
return null;
}
return node.getData();
}
/**
* Returns the names of the children of a node.
* @see AbstractDataTree#getNamesOfChildren(IPath)
*/
@Override
public String[] getNamesOfChildren(IPath parentKey) {
DataTreeNode parentNode;
parentNode = findNodeAt(parentKey);
if (parentNode == null) {
handleNotFound(parentKey);
return null;
}
return parentNode.namesOfChildren();
}
/**
* Returns the root node of the tree
*/
@Override
AbstractDataTreeNode getRootNode() {
return rootNode;
}
/**
* Returns true if the receiver includes a node with
* the given key, false otherwise.
*/
@Override
public boolean includes(IPath key) {
return (findNodeAt(key) != null);
}
/**
* Returns an object containing:
* - 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.
*/
@Override
public DataTreeLookup lookup(IPath key) {
DataTreeNode node = this.findNodeAt(key);
if (node == null)
return DataTreeLookup.newLookup(key, false, null);
return DataTreeLookup.newLookup(key, true, node.getData());
}
/**
* Replaces the node at the specified key with the given node
*/
protected void replaceNode(IPath key, DataTreeNode node) {
DataTreeNode found;
if (key.isRoot()) {
this.setRootNode(node);
} else {
found = this.findNodeAt(key.removeLastSegments(1));
found.replaceChild(key.lastSegment(), node);
}
}
/**
* Sets the data at the specified node.
* @see AbstractDataTree#setData(IPath, Object)
*/
@Override
public void setData(IPath key, Object data) {
DataTreeNode node = this.findNodeAt(key);
if (this.isImmutable())
handleImmutableTree();
if (node == null) {
handleNotFound(key);
} else {
node.setData(data);
}
}
/**
* Sets the root node of the tree
* @see AbstractDataTree#setRootNode(AbstractDataTreeNode)
*/
void setRootNode(DataTreeNode aNode) {
rootNode = aNode;
}
}