// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.util;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;
import java.util.Vector;
/**
* Implementation of a tree node that can form an n-ary tree.
* Provides basic operations such as getting/setting keys, values and child nodes,
* adding/removing child nodes or finding specific child nodes within the tree.
*
* @param <K> the type of the key to identifiy the node in a tree.
* @param <V> the type of the data associated with a tree node.
*/
public class MapTree<K, V> implements Cloneable
{
private final K key;
// private final Collection<MapTree<K, V>> children;
private final HashMap<K, MapTree<K, V>> children;
private MapTree<K, V> parent;
private V value;
/** Creates a path of MapTree objects, starting from the root node up to the specified node. */
public static <K, V> Collection<MapTree<K, V>> getNodePath(MapTree<K, V> node)
{
Collection<MapTree<K, V>> retVal = new Vector<MapTree<K, V>>();
if (node != null) {
Stack<MapTree<K, V>> stack = new Stack<MapTree<K,V>>();
MapTree<K, V> curNode = node;
while (curNode != null) {
stack.push(curNode);
curNode = curNode.getParent();
}
while (!stack.isEmpty()) {
retVal.add(stack.pop());
}
}
return retVal;
}
/** Returns the root of the specified node. */
public static <K, V> MapTree<K, V> getRoot(MapTree<K, V> node)
{
if (node != null) {
MapTree<K, V> curNode = node;
while (curNode.getParent() != null) {
curNode = curNode.getParent();
}
return curNode;
}
return null;
}
/**
* Constructs a new MapNode object with the given key and value arguments.
* @param key The node key.
* @param value The associated value.
*/
public MapTree(K key, V value)
{
if (key == null) {
throw new NullPointerException("key must not be null");
}
this.parent = null;
this.children = new HashMap<K, MapTree<K,V>>();
this.key = key;
this.value = value;
}
//--------------------- Begin Interface Cloneable ---------------------
/**
* Creates a copy of this node and all of its children.
* @return a clone of this node and all of its children.
*/
@Override
@SuppressWarnings("unchecked")
public Object clone()
{
MapTree<K, V> node = new MapTree<K, V>(key, value);
for (Iterator<MapTree<K, V>> iter = children.values().iterator(); iter.hasNext();) {
node.addChild((MapTree<K, V>)iter.next().clone());
}
return node;
}
//--------------------- End Interface Cloneable ---------------------
@Override
public boolean equals(Object o)
{
if (o instanceof MapTree<?, ?>) {
MapTree<?, ?> node = (MapTree<?, ?>)o;
boolean b = key.equals(node.getKey());
if (b && value != null) {
b &= value.equals(node.getValue());
}
return b;
}
return false;
}
@Override
public int hashCode()
{
int hash = key.hashCode();
if (value != null) {
hash ^= value.hashCode();
}
return hash;
}
/** Adds a new child to this node. May overwrite a child node containing a matching key. */
public boolean addChild(MapTree<K, V> child)
{
if (child != null) {
removeChild(child.getKey());
child.parent = this;
children.put(child.getKey(), child);
return true;
}
return false;
}
/**
* Adds all children of the given collection to this node. Existing child nodes
* containing a matching key will be overwritten. Returns the number of added child nodes.
*/
public int addChildren(Collection<MapTree<K, V>> children)
{
int retVal = 0;
if (children != null && !children.isEmpty()) {
for (Iterator<MapTree<K, V>> iter = children.iterator(); iter.hasNext();) {
MapTree<K, V> node = iter.next();
if (addChild(node)) {
retVal++;
}
}
}
return retVal;
}
/**
* Searches for the first node containing a matching key.
* @param key The key to search.
* @return The first available node containing the specified key.
*/
public MapTree<K, V> findNode(K key)
{
if (key != null) {
Collection<MapTree<K, V>> retVal = findNodesRecursive(null, this, key, true);
if (retVal != null && !retVal.isEmpty()) {
return retVal.iterator().next();
}
}
return null;
}
/**
* Searches the whole tree, starting from the current node for nodes containing a matching key.
* @param key The key to search.
* @return A collection of nodes containing the specified key.
*/
public Collection<MapTree<K, V>> findNodes(K key)
{
Collection<MapTree<K, V>> retVal = new Vector<MapTree<K, V>>();
if (key != null) {
retVal = findNodesRecursive(retVal, parent, key, false);
}
return retVal;
}
/** Returns the number of child nodes. */
public int getChildCount()
{
return children.size();
}
/** Returns the child node matching the given key, or null otherwise. */
public MapTree<K, V> getChild(K key)
{
if (key != null) {
return children.get(key);
}
return null;
}
/** Returns an unmodifiable collection of all children associated with this node. */
public Collection<MapTree<K, V>> getChildren()
{
return Collections.unmodifiableCollection(children.values());
}
/** Returns the node key. */
public K getKey()
{
return key;
}
/** Creates a node path, starting from the root node up to the current node. */
public Collection<MapTree<K, V>> getNodePath()
{
return getNodePath(this);
}
/** Returns the parent node (if any). */
public MapTree<K, V> getParent()
{
return parent;
}
/** Returns the value associated with the node (if any). */
public V getValue()
{
return value;
}
/**
* Removes the child node containing the matching key and returns it.
* Does nothing if no matching child node exists.
*/
public MapTree<K, V> removeChild(K key)
{
if (key != null) {
MapTree<K, V> node = children.remove(key);
if (node != null) {
node.parent = null;
}
return node;
}
return null;
}
/**
* Removes all children matching the keys in the given collection.
* Returns a collection of child nodes which have been successfully removed.
*/
public Collection<MapTree<K, V>> removeChildren(Collection<K> keys)
{
Collection<MapTree<K, V>> retVal = new Vector<MapTree<K, V>>();
if (keys != null && !keys.isEmpty()) {
for (Iterator<K> iter = keys.iterator(); iter.hasNext();) {
MapTree<K, V> node = removeChild(iter.next());
if (node != null) {
retVal.add(node);
}
}
}
return retVal;
}
/**
* Removes all children from the current node.
*/
public void removeAllChildren()
{
for (Iterator<K> iter = children.keySet().iterator(); iter.hasNext();) {
K key = iter.next();
MapTree<K, V> node = children.remove(key);
if (node != null) {
node.parent = null;
}
}
}
/**
* Replaces the value of the child node containing a matching key.
* Returns whether the operation was successful.
*/
public boolean setChild(K key, V value)
{
MapTree<K, V> node = getChild(key);
if (node != null) {
node.setValue(value);
return true;
}
return false;
}
/** Assigns a new value to this node. Returns the previously assigned value (if any). */
public V setValue(V newValue)
{
V retVal = value;
value = newValue;
return retVal;
}
// Recursively searches all child nodes for the given key.
// Returns either the first match only or a list of all available matches.
private static<K, V> Collection<MapTree<K, V>> findNodesRecursive(Collection<MapTree<K, V>> retVal,
MapTree<K, V> parent, K key,
boolean firstMatch)
{
if (retVal == null) {
retVal = new Vector<MapTree<K, V>>();
}
if (firstMatch && !retVal.isEmpty()) {
return retVal;
}
if (parent != null && key != null) {
for (Iterator<MapTree<K, V>> iter = parent.getChildren().iterator();
iter.hasNext() && (!firstMatch || retVal.isEmpty());) {
MapTree<K, V> node = iter.next();
if (node.getKey().equals(key)) {
retVal.add(node);
}
findNodesRecursive(retVal, node, key, firstMatch);
}
}
return retVal;
}
}