/*******************************************************************************
 * Copyright (c) 2000, 2006 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.internal.utils.Messages;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.osgi.util.NLS;

/**
 * Data trees can be viewed as generic multi-leaf trees.  The tree points to a single
 * rootNode, and each node can contain an arbitrary number of children.
 *
 * <p>Internally, data trees can be either complete trees (DataTree class), or delta
 * trees (<code>DeltaDataTree</code> class).  A DataTree is a stand-alone tree
 * that contains all its own data.  A <code>DeltaDataTree</code> only stores the
 * differences between itself and its parent tree.  This sparse representation allows
 * the API user to retain chains of delta trees that represent incremental changes to
 * a system.  Using the delta trees, the user can undo changes to a tree by going up to
 * the parent tree.
 *
 * <p>Both representations of the tree support the same API, so the user of a tree
 * never needs to know if they're dealing with a complete tree or a chain of deltas.
 * Delta trees support an extended API of delta operations.  See the <code>DeltaDataTree
 * </code> class for details.
 *
 * @see DataTree
 * @see DeltaDataTree
 */

public abstract class AbstractDataTree {

	/**
	 * Whether modifications to the given source tree are allowed
	 */
	private boolean immutable = false;

	/**
	 * Singleton indicating no children
	 */
	protected static final IPath[] NO_CHILDREN = new IPath[0];

	/**
	 * Creates a new empty tree
	 */
	public AbstractDataTree() {
		this.empty();
	}

	/**
	 * Returns a copy of the receiver, which shares the receiver's
	 * instance variables.
	 */
	protected AbstractDataTree copy() {
		AbstractDataTree newTree = this.createInstance();
		newTree.setImmutable(this.isImmutable());
		newTree.setRootNode(this.getRootNode());
		return newTree;
	}

	/**
	 * Returns a copy of the node subtree rooted at the given key.
	 *
	 */
	public abstract AbstractDataTreeNode copyCompleteSubtree(IPath key);

	/**
	 * Creates a new child in the tree.  If a child with such a name exists,
	 * it is replaced with the new child
	 *
	 * @param parentKey key of parent for new child.
	 * @param localName name for new child.
	 * @exception ObjectNotFoundException
	 *	parentKey does not exist in the receiver
	 * @exception RuntimeException
	 *	receiver is immutable
	 */
	public abstract void createChild(IPath parentKey, String localName);

	/**
	 * Creates a new child in the tree.  If a child with such a name exists,
	 * it is replaced with the new child
	 *
	 * @param parentKey key of parent for new child.
	 * @param localName name for new child.
	 * @param object the data for the new child
	 * @exception ObjectNotFoundException
	 *	parentKey does not exist in the receiver
	 * @exception RuntimeException
	 *	receiver is immutable
	 */
	public abstract void createChild(IPath parentKey, String localName, Object object);

	/**
	 * Creates and returns a new instance of the tree.  This is an
	 * implementation of the factory method creational pattern for allowing
	 * abstract methods to create instances.
	 *
	 * @return the new tree.
	 */
	protected abstract AbstractDataTree createInstance();

	/**
	 * Creates or replaces a subtree in the tree.  The parent node must exist.
	 *
	 * @param key key of parent of subtree to create/replace
	 * @param subtree new subtree to add to tree
	 * @exception RuntimeException receiver is immutable
	 */
	public abstract void createSubtree(IPath key, AbstractDataTreeNode subtree);

	/**
	 * Deletes a child from the tree.
	 *
	 * <p>Note: this method requires both parentKey and localName,
	 * making it impossible to delete the root node.
	 *
	 * @param parentKey parent of node to delete.
	 * @param localName name of node to delete.
	 * @exception ObjectNotFoundException
	 *	a child of parentKey with name localName does not exist in the receiver
	 * @exception RuntimeException
	 *	receiver is immutable
	 */
	public abstract void deleteChild(IPath parentKey, String localName);

	/**
	 * Initializes the receiver so that it is a complete, empty tree.  The
	 * result does not represent a delta on another tree.  An empty tree is
	 * defined to have a root node with null data and no children.
	 */
	public abstract void empty();

	/**
	 * Returns the key of a node in the tree.
	 *
	 * @param parentKey
	 *	parent of child to retrieve.
	 * @param index
	 *	index of the child to retrieve in its parent.
	 * @exception ObjectNotFoundException
	 * 	parentKey does not exist in the receiver
	 * @exception ArrayIndexOutOfBoundsException
	 *	if no child with the given index (runtime exception)
	 */
	public IPath getChild(IPath parentKey, int index) {
		/* Get name of given child of the parent */
		String child = getNameOfChild(parentKey, index);
		return parentKey.append(child);
	}

	/**
	 * Returns the number of children of a node
	 *
	 * @param parentKey
	 *	key of the node for which we want to retreive the number of children
	 * @exception ObjectNotFoundException
	 *	parentKey does not exist in the receiver
	 */
	public int getChildCount(IPath parentKey) {
		return getNamesOfChildren(parentKey).length;
	}

	/**
	 * Returns the keys of all children of a node.
	 *
	 * @param parentKey
	 *	key of parent whose children we want to retrieve.
	 * @exception ObjectNotFoundException
	 *	parentKey does not exist in the receiver
	 */
	public IPath[] getChildren(IPath parentKey) {
		String names[] = getNamesOfChildren(parentKey);
		int len = names.length;
		if (len == 0)
			return NO_CHILDREN;
		IPath answer[] = new IPath[len];

		for (int i = 0; i < len; i++) {
			answer[i] = parentKey.append(names[i]);
		}
		return answer;
	}

	/**
	 * Returns the data of a node.
	 *
	 * @param key
	 *	key of node for which we want to retrieve data.
	 * @exception ObjectNotFoundException
	 *	key does not exist in the receiver
	 */
	public abstract Object getData(IPath key);

	/**
	 * Returns the local name of a node in the tree
	 *
	 * @param parentKey
	 *	parent of node whose name we want to retrieve
	 * @param index
	 *	index of node in its parent
	 * @exception ObjectNotFoundException
	 *	parentKey does not exist in the receiver
	 * @exception ArrayIndexOutOfBoundsException
	 *	if no child with the given index
	 */
	public String getNameOfChild(IPath parentKey, int index) {
		String childNames[] = getNamesOfChildren(parentKey);
		/* Return the requested child as long as its in range */
		return childNames[index];
	}

	/**
	 * Returns the local names for the children of a node
	 *
	 * @param parentKey
	 *	key of node whose children we want to retrieve
	 * @exception ObjectNotFoundException
	 *	parentKey does not exist in the receiver
	 */
	public abstract String[] getNamesOfChildren(IPath parentKey);

	/**
	 * Returns the root node of the tree.
	 *
	 * <p>Both subclasses must be able to return their root node.  However subclasses
	 * can have different types of root nodes, so this is not enforced as an abstract
	 * method
	 */
	AbstractDataTreeNode getRootNode() {
		throw new AbstractMethodError(Messages.dtree_subclassImplement);
	}

	/**
	 * Handles the case where an attempt was made to modify
	 * the tree when it was in an immutable state.  Throws
	 * an unchecked exception.
	 */
	static void handleImmutableTree() {
		throw new RuntimeException(Messages.dtree_immutable);
	}

	/**
	 * Handles the case where an attempt was made to manipulate
	 * an element in the tree that does not exist.  Throws an
	 * unchecked exception.
	 */
	static void handleNotFound(IPath key) {
		throw new ObjectNotFoundException(NLS.bind(Messages.dtree_notFound, key));
	}

	/**
	 * Makes the tree immutable
	 */
	public void immutable() {
		immutable = true;
	}

	/**
	 * Returns true if the receiver includes a node with the given key, false
	 * otherwise.
	 *
	 * @param key
	 *	key of node to find
	 */
	public abstract boolean includes(IPath key);

	/**
	 * Returns true if the tree is immutable, and false otherwise.
	 */
	public boolean isImmutable() {
		return immutable;
	}

	/**
	 * 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.
	 */
	public abstract DataTreeLookup lookup(IPath key);

	/**
	 * Returns the key of the root node.
	 */
	public IPath rootKey() {
		return Path.ROOT;
	}

	/**
	 * Sets the data of a node.
	 *
	 * @param key
	 *	key of node for which to set data
	 * @param data
	 *	new data value for node
	 * @exception ObjectNotFoundException
	 *	the nodeKey does not exist in the receiver
	 * @exception IllegalArgumentException
	 *	receiver is immutable
	 */
	public abstract void setData(IPath key, Object data);

	/**
	 * Sets the immutable field.
	 */
	void setImmutable(boolean bool) {
		immutable = bool;
	}

	/**
	 * Sets the root node of the tree.
	 *
	 * <p>Both subclasses must be able to set their root node.  However subclasses
	 * can have different types of root nodes, so this is not enforced as an abstract
	 * method
	 */
	void setRootNode(AbstractDataTreeNode node) {
		throw new Error(Messages.dtree_subclassImplement);
	}
}
