Click here to Skip to main content
15,891,993 members
Articles / Programming Languages / C#

Generic Tree Control

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
8 Feb 2009CPOL2 min read 25K   184   16  
An article on a generic tree collection
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.Serialization;


namespace Precio.Collections
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TNodeValue"></typeparam>
    /// <author>Martin Dahlstedt 2006 (m_dahlstedt@hotmail.com)</author>
    [Serializable()]
    public class Tree<TNodeValue> : IEnumerable<TreeNode<TNodeValue>>, ICollection<TreeNode<TNodeValue>>, IList<TreeNode<TNodeValue>>, IEnumerable, ICollection, IList, ISerializable
        where TNodeValue : TreeNodeValue
    {
        private TreeNode<TNodeValue> _rootNode = new TreeNode<TNodeValue>();

        protected Boolean _treeIndexed = false;

        private Int32 _indexes = 0;

        private Int32 _levels = 0;

        public event EventHandler<NodeIndexedEventArgs<TNodeValue>> NodeIndexedEvent;

        public event EventHandler<IndexCalculationFinishedEventArgs<TNodeValue>> IndexCalculationFinishedEvent;


        /// <summary>
        /// Gets the root node.
        /// </summary>
        public TreeNode<TNodeValue> RootNode
        {
            get
            {
                return _rootNode;
            }
        }


        /// <summary>
        /// Gets the count of nodes in the tree collection.
        /// </summary>
        public Int32 Count
        {
            get
            {
                return _indexes;
            }
        }


        public Int32 Levels
        {
            get
            {
                return _levels;
            }
        }


        /// <summary>
        /// Constructor for Tree.
        /// </summary>
        /// <param name="root"></param>
        public Tree(TNodeValue root)
        {
            _rootNode.NodeValue = root;
        }


        /// <summary>
        /// 
        /// </summary>
        public void BuildIndex()
        {
            BuildIndexInternal();

            _treeIndexed = true;

            IndexCalculationFinishedEventArgs<TNodeValue> args = new IndexCalculationFinishedEventArgs<TNodeValue>(_indexes);

            FireIndexCalculationFinishedEvent(args);
        }


        protected virtual void BuildIndexInternal()
        {

        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public Boolean TrueForAll(Predicate<TNodeValue> predicate)
        {
            foreach (Pair<Int32, TNodeValue> nodePair in this)
            {
                if (!predicate(nodePair.Second))
                {
                    return false;
                }
            }

            return true;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        public void Filter(Predicate<TNodeValue> predicate)
        {
            MakeFilterIndex(predicate);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        private Int32[] MakeFilterIndex(Predicate<TNodeValue> predicate)
        {
            List<Int32> ret = new List<Int32>();
            Int32 i = 0;

            foreach (Pair<Int32, TNodeValue> nodePair in this)
            {
                if (predicate(nodePair.Second))
                {
                    ret.Add(i);
                }

                i++;
            }


            return ret.ToArray();
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        /// <param name="ignoreRoot"></param>
        public void ActionAll(Action<TNodeValue> action, Boolean ignoreRoot)
        {
            foreach (TreeNode<TNodeValue> nodePair in this)
            {
                if (ignoreRoot & nodePair.Level < 1)
                    continue;

                action(nodePair.NodeValue);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="action"></param>
        /// <param name="ignoreRoot"></param>
        public void ActionIf(Predicate<TNodeValue> predicate, Action<TNodeValue> action, Boolean ignoreRoot)
        {
            foreach (TreeNode<TNodeValue> nodePair in this)
            {
                if (ignoreRoot & nodePair.Level < 1)
                    continue;

                if (predicate(nodePair.NodeValue))
                {
                    action(nodePair.NodeValue);
                }
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="action"></param>
        /// <param name="ignoreRoot"></param>
        public void ActionIfNot(Predicate<TNodeValue> predicate, Action<TNodeValue> action, Boolean ignoreRoot)
        {
            foreach (TreeNode<TNodeValue> nodePair in this)
            {
                if (ignoreRoot & nodePair.Level < 1)
                    continue;

                if (!predicate(nodePair.NodeValue))
                {
                    action(nodePair.NodeValue);
                }
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="tree"></param>
        /// <returns></returns>
        public Tree<TNodeValue> Merge(Tree<TNodeValue> tree)
        {
            Tree<TNodeValue> mergedTree = new Tree<TNodeValue>(this.RootNode.NodeValue);

            foreach (TreeNode<TNodeValue> nodeInformation in this)
            {
                // skip the most top node
                if (nodeInformation.Level < 1)
                    continue;

                mergedTree.AddLast(nodeInformation.Parent.RelativePath, nodeInformation.NodeValue);
            }

            foreach (TreeNode<TNodeValue> nodePair in tree)
            {
                // skip the most top node
                if (nodePair.Level < 1)
                    continue;

                mergedTree.AddLast(nodePair.Parent.RelativePath, nodePair.NodeValue);
            }

            return mergedTree;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="tree"></param>
        /// <param name="Equals"></param>
        /// <returns></returns>
        public CompareTree<TNodeValue> CompareTo(Tree<TNodeValue> tree, Predicate<CompareTreeArgs<TNodeValue>> comparePredicate)
        {
            CompareTree<TNodeValue> compareTree = new CompareTree<TNodeValue>(this.RootNode.NodeValue);

            InternalCompareTo(this, tree, ref compareTree, comparePredicate, true);

            InternalCompareTo(tree, this, ref compareTree, comparePredicate, false);

            return compareTree;
        }


        public TreeNode<TNodeValue> this[String relativePath]
        {
            get
            {
                foreach (TreeNode<TNodeValue> nodePair in this)
                {
                    if (String.Compare(nodePair.RelativePath, relativePath, StringComparison.OrdinalIgnoreCase) == 0)
                        return nodePair;
                }

                throw new NullReferenceException(String.Format(@"Could not find the node information on the relative path '{0}'.", relativePath));
            }
        }


        private void InternalCompareTo(Tree<TNodeValue> tree1, Tree<TNodeValue> tree2, ref CompareTree<TNodeValue> compareTree, Predicate<CompareTreeArgs<TNodeValue>> comparePredicate, Boolean isSource)
        {
            foreach (TreeNode<TNodeValue> nodePair1 in tree1)
            {
                // skip the most top node
                if (nodePair1.Level < 1)
                    continue;


                CompareNodeValue<TNodeValue> compareNode = null;

                foreach (TreeNode<TNodeValue> nodePair2 in tree2)
                {
                    // skip the most top node
                    if (nodePair2.Level < 1)
                        continue;


                    if (!nodePair1.RelativePath.Equals(nodePair2.RelativePath, StringComparison.OrdinalIgnoreCase))
                        continue;


                    if (!nodePair1.Name.Equals(nodePair2.Name, StringComparison.OrdinalIgnoreCase))
                        continue;

                    CompareTreeArgs<TNodeValue> args = new CompareTreeArgs<TNodeValue>(nodePair1.NodeValue, nodePair2.NodeValue);

                    CompareNodeValue<TNodeValue>.CompareStatus compareStatus = comparePredicate(args) ? CompareNodeValue<TNodeValue>.CompareStatus.Equal : CompareNodeValue<TNodeValue>.CompareStatus.Different;

                    compareNode = new CompareNodeValue<TNodeValue>(nodePair1.NodeValue, nodePair1.NodeValue.Path, compareStatus);

                    break;
                }

                //Check if we need to add the node since it was missing in the second folder.
                if (compareNode == null)
                {
                    CompareNodeValue<TNodeValue>.CompareStatus compareStatus = isSource ? CompareNodeValue<TNodeValue>.CompareStatus.MissingInDestination : CompareNodeValue<TNodeValue>.CompareStatus.MissingInSource;

                    compareNode = new CompareNodeValue<TNodeValue>(nodePair1.NodeValue, nodePair1.NodeValue.Path, compareStatus);
                }

                compareTree.AddLast(nodePair1.Parent.RelativePath, compareNode);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="parentRelativePath"></param>
        /// <param name="nodeValue"></param>
        public TreeNode<TNodeValue> AddLast(String parentRelativePath, TNodeValue nodeValue)
        {
            TreeNode<TNodeValue> parentNode = GetTreeNodeByRelativePath(_rootNode, parentRelativePath);

            if (parentNode == null)
                throw new NullReferenceException(String.Format(@"Could not find the parent root by the relative path {0}.", parentRelativePath));

            return this.AddLast(parentNode, nodeValue);
        }


        private TreeNode<TNodeValue> GetTreeNodeByRelativePath(TreeNode<TNodeValue> parentNode, String relativePath)
        {
            if (parentNode.RelativePath == relativePath)
                return parentNode;


            TreeNode<TNodeValue> foundTreeNode = null;

            foreach (TreeNode<TNodeValue> childNode in parentNode.ChildNodes)
            {
                foundTreeNode = GetTreeNodeByRelativePath(childNode, relativePath);

                if (foundTreeNode != null)
                    break;
            }

            return foundTreeNode;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="parentNode">Parent node in which the new node is to be created on.</param>
        /// <param name="nodeValue">The value of the new node.</param>
        public TreeNode<TNodeValue> AddLast(TreeNode<TNodeValue> parentNode, TNodeValue nodeValue)
        {
            String relativePath = String.Format(@"{0}\{1}", parentNode.RelativePath, nodeValue.Name);

            relativePath = relativePath.TrimStart('\\');

            TreeNode<TNodeValue> newNode = new TreeNode<TNodeValue>(nodeValue, parentNode, nodeValue.Name, relativePath);


            //Check to see there is already a space on that relative path.
            foreach (TreeNode<TNodeValue> childNode in parentNode.ChildNodes)
            {
                if (childNode.RelativePath == relativePath)
                    return null;
            }

            parentNode.ChildNodes.AddLast(newNode);

            _indexes++;


            Int32 levels = String.IsNullOrEmpty(newNode.RelativePath) ? 0 : newNode.RelativePath.Split('\\').Length;

            _levels = levels > _levels ? levels : _levels;

            NodeIndexedEventArgs<TNodeValue> args = new NodeIndexedEventArgs<TNodeValue>(newNode);

            FireNewNodeAddedEvent(args);

            return newNode;
        }


        public Boolean Remove(TreeNode<TNodeValue> treeNode)
        {
            TreeNode<TNodeValue> nodeToRemove = GetTreeNodeByRelativePath(_rootNode, treeNode.RelativePath);

            Boolean result = nodeToRemove.Parent.ChildNodes.Remove(nodeToRemove);

            if (result)
            {
                _indexes--;
            }

            return result;
        }


        private IEnumerator<TreeNode<TNodeValue>> GetEnumerator(TreeNode<TNodeValue> treeNode)
        {
            //Return the first (root) node first.
            yield return treeNode;

            LinkedListNode<TreeNode<TNodeValue>> linkedList = treeNode.ChildNodes.First;

            while (linkedList != null)
            {
                IEnumerator<TreeNode<TNodeValue>> enumerator = GetEnumerator(linkedList.Value);

                while (enumerator.MoveNext())
                {
                    yield return enumerator.Current;
                }

                linkedList = linkedList.Next;
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        private void FireNewNodeAddedEvent(NodeIndexedEventArgs<TNodeValue> e)
        {
            if (this.NodeIndexedEvent != null)
            {
                this.NodeIndexedEvent(this, e);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        private void FireIndexCalculationFinishedEvent(IndexCalculationFinishedEventArgs<TNodeValue> e)
        {
            if (this.IndexCalculationFinishedEvent != null)
            {
                this.IndexCalculationFinishedEvent(this, e);
            }
        }

        #region IList<Pair<TreeNodePathInfo,T>> Members

        public int IndexOf(TreeNode<TNodeValue> treeNode)
        {
            Int32 currentIndex = 0;

            foreach (TreeNode<TNodeValue> temp in this)
            {
                if (temp.NodeValue == treeNode.NodeValue)
                    return currentIndex;

                currentIndex++;
            }

            return -1;
        }


        public void Insert(int index, TreeNode<TNodeValue> treeNode)
        {
            throw new NotImplementedException();
        }


        public void RemoveAt(int index)
        {
            TreeNode<TNodeValue> treeNode = this[index];

            this.Remove(treeNode);
        }


        public TreeNode<TNodeValue> this[int index]
        {
            get
            {
                Int32 currentIndex = 0;

                foreach (TreeNode<TNodeValue> treeNode in this)
                {
                    if (currentIndex == index)
                        return treeNode;

                    currentIndex++;
                }

                throw new NullReferenceException(String.Format(@"Could not find the node information on index {0}.", index));
            }

            set
            {
                TreeNode<TNodeValue> nodePair = this[index];

                nodePair = value;
            }
        }

        #endregion

        #region IList Members

        public int Add(object value)
        {
            this.Add((TreeNode<TNodeValue>)value);

            return 1;
        }

        public Boolean Contains(object value)
        {
            return this.Contains(value as TreeNode<TNodeValue>);
        }

        public Int32 IndexOf(object value)
        {
            throw new NotImplementedException();
        }

        public void Insert(int index, object value)
        {
            throw new NotImplementedException();
        }

        public Boolean IsFixedSize
        {
            get { throw new NotImplementedException(); }
        }

        public void Remove(object value)
        {
            throw new NotImplementedException();
        }

        object IList.this[int index]
        {
            get
            {
                throw new NotImplementedException();
            }

            set
            {
                throw new NotImplementedException();
            }
        }

        #endregion

        #region IEnumerable<TreeNode<TNodeValue>> Members

        IEnumerator<TreeNode<TNodeValue>> IEnumerable<TreeNode<TNodeValue>>.GetEnumerator()
        {
            return GetEnumerator(_rootNode);
        }

        #endregion

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return GetEnumerator(_rootNode);
        }

        #endregion

        #region ICollection<Pair<TreeNodePathInfo,T>> Members

        public void Add(TreeNode<TNodeValue> treeNode)
        {
            TreeNode<TNodeValue> parentTreeNode = GetTreeNodeByRelativePath(_rootNode, treeNode.Parent.RelativePath);

            if (parentTreeNode == null)
                throw new NullReferenceException(String.Format(@"Could not find the parent root by the relative path {0}.", treeNode.RelativePath));

            this.AddLast(parentTreeNode, treeNode.NodeValue);
        }


        public void Clear()
        {
            _rootNode = new TreeNode<TNodeValue>();
        }


        public Boolean Contains(TreeNode<TNodeValue> treeNode)
        {
            foreach (TreeNode<TNodeValue> temp in this)
            {
                if (temp.NodeValue == treeNode.NodeValue)
                    return true;
            }

            return false;
        }


        public void CopyTo(TreeNode<TNodeValue>[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }


        public Boolean IsReadOnly
        {
            get
            {
                return false;
            }
        }

        #endregion

        #region ICollection Members

        public void CopyTo(Array array, int index)
        {
            throw new NotImplementedException();
        }

        public bool IsSynchronized
        {
            get
            {
                return false;
            }
        }

        public object SyncRoot
        {
            get { throw new NotImplementedException(); }
        }

        #endregion

        #region ISerializable Members

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) B3IT
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions