Click here to Skip to main content
15,892,480 members
Articles / Programming Languages / C#

Resolving Symbolic References in a CodeDOM (Part 7)

Rate me:
Please Sign up or sign in to vote.
4.75/5 (6 votes)
2 Dec 2012CDDL12 min read 19.4K   509   14  
Resolving symbolic references in a CodeDOM.
// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Collections.Generic;
using Mono.Cecil;

using Nova.Rendering;
using Nova.Resolving;
using Nova.Utilities;

namespace Nova.CodeDOM
{
    /// <summary>
    /// Represents a namespace of type declarations and optional child namespaces.
    /// </summary>
    /// <remarks>
    /// Unlike <see cref="NamespaceDecl"/>, which models individual namespace declaration statements and their contained statements,
    /// there is only a single instance of this class for a namespace, and it contains all of the <see cref="TypeDecl"/>
    /// (for types declared in the same solution) and/or <see cref="TypeDefinition"/>/<see cref="Type"/> (for external types) and
    /// child <see cref="Namespace"/> objects that currently exist in the namespace.
    /// The Parent of a Namespace instance should only be another Namespace, but the Parent of a <see cref="RootNamespace"/>
    /// can be either a <see cref="Project"/> (global namespace) or a <see cref="Reference"/> (extern alias namespace).
    /// </remarks>
    public class Namespace : CodeObject, INamedCodeObject, INamespace
    {
        #region /* FIELDS */

        /// <summary>
        /// The name of the Namespace (does not include any parent Namespaces).
        /// </summary>
        protected string _name;

        /// <summary>
        /// The full name of the Namespace (including any parent Namespaces).
        /// </summary>
        /// <remarks>
        /// This full name must be re-generated if any Namespace in it is renamed or moved.
        /// It's used to improve performance, so that the same string doesn't have to be
        /// generated many times during symbol resolution.
        /// </remarks>
        protected string _fullName;

        /// <summary>
        /// True if this namespace has <see cref="NamespaceDecl"/> declarations in the current project,
        /// otherwise false (meaning items in the namespace exist only in imported assemblies
        /// and projects).
        /// </summary>
        protected bool _hasDeclarationsInProject;

        /// <summary>
        /// Dictionary of child namespaces and types (<see cref="Namespace"/>s, <see cref="TypeDecl"/>s, and <see cref="TypeDefinition"/>s/<see cref="Type"/>s) by name.
        /// </summary>
        protected NamespaceTypeDictionary _children = new NamespaceTypeDictionary();

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a <see cref="Namespace"/> with the specified name and parent.
        /// </summary>
        public Namespace(string name, CodeObject parent)
        {
            _name = name;
            Parent = parent;
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The name of the <see cref="Namespace"/>.
        /// </summary>
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                UpdateFullName();
            }
        }

        /// <summary>
        /// The full name of the <see cref="Namespace"/>, including any parent namespaces.
        /// </summary>
        public string FullName
        {
            get { return _fullName; }
        }

        /// <summary>
        /// The descriptive category of the code object.
        /// </summary>
        public string Category
        {
            get { return "namespace"; }
        }

        /// <summary>
        /// The dictionary of child objects in the <see cref="Namespace"/>.
        /// </summary>
        public NamespaceTypeDictionary Children
        {
            get { return _children; }
        }

        /// <summary>
        /// The parent <see cref="CodeObject"/>.
        /// </summary>
        public override CodeObject Parent
        {
            set
            {
                base.Parent = value;
                UpdateFullName();
            }
        }

        /// <summary>
        /// Determines if this <see cref="Namespace"/> is root-level (global or extern alias).
        /// </summary>
        public virtual bool IsRootLevel
        {
            get { return false; }
        }

        /// <summary>
        /// Determines if this <see cref="Namespace"/> is the project-global namespace.
        /// </summary>
        public virtual bool IsGlobal
        {
            get { return false; }
        }

        /// <summary>
        /// True if this <see cref="Namespace"/> has <see cref="NamespaceDecl"/> declarations in the current project, otherwise
        /// false (meaning items in the namespace exist only in imported assemblies and projects).
        /// </summary>
        public bool HasDeclarationsInProject
        {
            get { return _hasDeclarationsInProject; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Add the specified child <see cref="Namespace"/> to the current <see cref="Namespace"/>.
        /// </summary>
        public void Add(Namespace @namespace)
        {
            lock (this)
            {
                _children.Add(@namespace);
                @namespace.Parent = this;
            }
        }

        /// <summary>
        /// Add the specified <see cref="TypeDecl"/> to the current <see cref="Namespace"/>.
        /// </summary>
        public void Add(TypeDecl typeDecl)
        {
            lock (this)
                _children.Add(typeDecl);
        }

        /// <summary>
        /// Add the specified <see cref="TypeDefinition"/> to the current <see cref="Namespace"/>.
        /// </summary>
        public void Add(TypeDefinition typeDefinition)
        {
            lock (this)
                _children.Add(typeDefinition);
        }

        /// <summary>
        /// Add the specified <see cref="Type"/> to the current <see cref="Namespace"/>.
        /// </summary>
        public void Add(Type type)
        {
            lock (this)
                _children.Add(type);
        }

        protected internal void AddFromOtherProject(Project project, Namespace @namespace, bool includePrivateTypes)
        {
            lock (this)
            {
                foreach (object codeObject in @namespace.Children)
                {
                    if (codeObject is NamespaceTypeGroup)
                    {
                        foreach (object codeObject2 in (NamespaceTypeGroup)codeObject)
                            AddFromOtherProject(project, codeObject2, includePrivateTypes);
                    }
                    else
                        AddFromOtherProject(project, codeObject, includePrivateTypes);
                }
            }
        }

        private void AddFromOtherProject(Project project, object codeObject, bool includePrivateTypes)
        {
            if (codeObject is TypeDecl)
            {
                // Add only those TypeDecls that are declared in the other project, and are accessible
                TypeDecl typeDecl = (TypeDecl)codeObject;
                if (typeDecl.ParentProject == project && (includePrivateTypes || typeDecl.IsPublic || typeDecl.IsProtected))
                    Add(typeDecl);
            }
            else if (codeObject is Namespace)
            {
                // Add only those Namespaces that have declarations in the other project
                Namespace @namespace = (Namespace)codeObject;
                if (@namespace.HasDeclarationsInProject)
                {
                    Namespace childNamespace = FindOrCreateChildNamespace(@namespace.Name);
                    childNamespace.AddFromOtherProject(project, @namespace, includePrivateTypes);
                }
            }
        }

        /// <summary>
        /// Remove the specified child <see cref="Namespace"/> from the current <see cref="Namespace"/>.
        /// </summary>
        public void Remove(Namespace @namespace)
        {
            lock (this)
                _children.Remove(@namespace);
        }

        /// <summary>
        /// Remove the specified <see cref="TypeDecl"/> from the current <see cref="Namespace"/>.
        /// </summary>
        public void Remove(TypeDecl typeDecl)
        {
            lock (this)
                _children.Remove(typeDecl);
        }

        /// <summary>
        /// Remove the specified <see cref="TypeDefinition"/> from the current <see cref="Namespace"/>.
        /// </summary>
        public void Remove(TypeDefinition typeDefinition)
        {
            lock (this)
                _children.Remove(typeDefinition);
        }

        /// <summary>
        /// Remove the specified <see cref="Type"/> from the current <see cref="Namespace"/>.
        /// </summary>
        public void Remove(Type type)
        {
            lock (this)
                _children.Remove(type);
        }

        /// <summary>
        /// Remove all items from the <see cref="Namespace"/>.
        /// </summary>
        public virtual void RemoveAll()
        {
            lock (this)
                _children.Clear();
        }

        /// <summary>
        /// Create a reference to the <see cref="Namespace"/>.
        /// </summary>
        /// <param name="isFirstOnLine">True if the reference should be displayed on a new line.</param>
        /// <returns>A <see cref="NamespaceRef"/>.</returns>
        public override SymbolicRef CreateRef(bool isFirstOnLine)
        {
            return new NamespaceRef(this, isFirstOnLine);
        }

        protected virtual void NamespaceCreated(Namespace @namespace)
        {
            if (_parent != null)
                ((Namespace)_parent).NamespaceCreated(@namespace);
        }

        /// <summary>
        /// Find or create a child <see cref="Namespace"/>, including any missing parent namespaces.
        /// </summary>
        public virtual Namespace FindOrCreateChildNamespace(string namespaceName)
        {
            Namespace @namespace = null;
            string prefix = RemovePrefix(ref namespaceName);

            lock (this)
            {
                object obj = _children.Find(prefix);
                if (obj is Namespace)
                    @namespace = (Namespace)obj;
                else if (obj is NamespaceTypeGroup)
                {
                    foreach (object childObj in (NamespaceTypeGroup)obj)
                    {
                        if (childObj is Namespace)
                        {
                            @namespace = (Namespace)childObj;
                            break;
                        }
                    }
                }
                if (@namespace == null)
                {
                    @namespace = new Namespace(prefix, this);
                    Add(@namespace);
                    NamespaceCreated(@namespace);
                }
            }

            if (!string.IsNullOrEmpty(namespaceName))
                @namespace = @namespace.FindOrCreateChildNamespace(namespaceName);
            return @namespace;
        }

        /// <summary>
        /// Find a child <see cref="Namespace"/>, <see cref="TypeDecl"/>, or <see cref="TypeDefinition"/>/<see cref="Type"/> with
        /// the specified name.
        /// </summary>
        /// <param name="name">The name of the child namespace or type (may contain namespace and/or parent type prefixes).</param>
        /// <returns>The matching <see cref="TypeDecl"/>, <see cref="TypeDefinition"/>/<see cref="Type"/>, <see cref="Namespace"/>,
        /// <see cref="NamespaceTypeGroup"/>, or null if not found.</returns>
        public object Find(string name)
        {
            string prefix = RemovePrefix(ref name);
            object obj;
            lock (this)
                obj = _children.Find(prefix);
            if (string.IsNullOrEmpty(name))
                return obj;

            // Handle names with namespace prefixes
            if (obj is Namespace)
                return ((Namespace)obj).Find(name);

            // Handle nested types
            if (obj is TypeDecl)
                return ((TypeDecl)obj).GetNestedType(name);
            if (obj is TypeDefinition)
                return TypeDefinitionUtil.GetNestedType((TypeDefinition)obj, name);
            if (obj is Type)
                return ((Type)obj).GetNestedType(name);

            return null;
        }

        /// <summary>
        /// Get an enumerator for all children objects of type <typeparamref name="T"/> in
        /// the <see cref="Namespace"/> and in any child Namespaces (recursively).
        /// </summary>
        /// <typeparam name="T">May be <see cref="TypeDecl"/> (or a derived type), <see cref="Type"/>,
        /// <see cref="Namespace"/>, or <see cref="object"/> to return objects of all types.</typeparam>
        public IEnumerable<T> GetAllChildren<T>()
        {
            foreach (object obj in _children)
            {
                if (obj is T)
                    yield return (T)obj;
                if (obj is Namespace)
                {
                    foreach (T typeDecl in ((Namespace)obj).GetAllChildren<T>())
                        yield return typeDecl;
                }
            }
        }

        /// <summary>
        /// Parse the specified name into a child <see cref="NamespaceRef"/> or <see cref="TypeRef"/> on the current namespace,
        /// or a <see cref="Dot"/> expression that evaluates to one.
        /// </summary>
        public virtual Expression ParseName(string name)
        {
            string prefix = RemovePrefix(ref name);
            SymbolicRef symbolicRef;
            object obj;
            lock (this)
                obj = _children.Find(prefix);
            if (obj != null)
            {
                if (obj is Namespace)
                    symbolicRef = new NamespaceRef((Namespace)obj);
                else if (obj is ITypeDecl)
                    symbolicRef = new TypeRef((ITypeDecl)obj);
                else if (obj is TypeDefinition)
                    symbolicRef = new TypeRef((TypeDefinition)obj);
                else //if (obj is Type)
                    symbolicRef = new TypeRef((Type)obj);
            }
            else
                symbolicRef = new UnresolvedRef(prefix);
            return (string.IsNullOrEmpty(name) ? symbolicRef : ParseName(symbolicRef, name));
        }

        /// <summary>
        /// Parse the specified name into a child <see cref="NamespaceRef"/> or <see cref="TypeRef"/> on the existing
        /// <see cref="Lookup"/> or <see cref="Dot"/> expression.
        /// </summary>
        protected Expression ParseName(Expression expression, string name)
        {
            Expression rightMost = expression.SkipPrefixes();
            do
            {
                string prefix = RemovePrefix(ref name);
                SymbolicRef newRight;
                if (rightMost is NamespaceRef)
                    newRight = (SymbolicRef)((NamespaceRef)rightMost).Namespace.ParseName(prefix);
                else if (rightMost is TypeRef)
                    newRight = ((TypeRef)rightMost).GetNestedType(prefix);
                else
                    newRight = new UnresolvedRef(prefix);
                expression = new Dot(expression, newRight);
                rightMost = newRight;
            }
            while (!string.IsNullOrEmpty(name));
            return expression;
        }

        /// <summary>
        /// Remove any prefix from the input string (delimited by a '.' or a '+').
        /// </summary>
        /// <param name="input">The input string - with prefix removed on output (empty string if no prefix existed).</param>
        /// <returns>The prefix if any, otherwise the input string.</returns>
        public static string RemovePrefix(ref string input)
        {
            string prefix;
            int dot = input.IndexOfAny(new[] { '.', '+' });
            if (dot > 0)
            {
                prefix = input.Substring(0, dot);
                input = input.Substring(dot + 1);
            }
            else
            {
                prefix = input;
                input = "";
            }
            return prefix;
        }

        protected internal void SetDeclarationsInProject(bool hasDeclarationsInProject)
        {
            _hasDeclarationsInProject = hasDeclarationsInProject;
        }

        /// <summary>
        /// Add the <see cref="CodeObject"/> to the specified dictionary.
        /// </summary>
        public void AddToDictionary(NamedCodeObjectDictionary dictionary)
        {
            dictionary.Add(Name, this);
        }

        /// <summary>
        /// Remove the <see cref="CodeObject"/> from the specified dictionary.
        /// </summary>
        public void RemoveFromDictionary(NamedCodeObjectDictionary dictionary)
        {
            dictionary.Remove(Name, this);
        }

        /// <summary>
        /// Update the FullName (called when the Name or Parent is changed).
        /// </summary>
        protected virtual void UpdateFullName()
        {
            _fullName = (((Namespace)_parent).IsRootLevel ? _name : ((Namespace)_parent).FullName + "." + _name);
        }

        /// <summary>
        /// Get the full name of the <see cref="Namespace"/>, including any parent namespace names.
        /// </summary>
        public string GetFullName(bool descriptive)
        {
            return _fullName;
        }

        /// <summary>
        /// Get the full name of the <see cref="Namespace"/>, including any parent namespace names.
        /// </summary>
        public string GetFullName()
        {
            return _fullName;
        }

        #endregion

        #region /* RESOLVING */

        /// <summary>
        /// Resolve child code objects that match the specified name.
        /// </summary>
        public void ResolveRef(string name, Resolver resolver, bool noNamespaces)
        {
            // Search the namespace for the name
            object found = _children.Find(name);
            if (found != null)
            {
                if (found is NamespaceTypeGroup)
                {
                    foreach (object subObj in (NamespaceTypeGroup)found)
                    {
                        if (!(noNamespaces && subObj is Namespace))
                            resolver.AddMatch(subObj);
                    }
                }
                else if (!(noNamespaces && found is Namespace))
                    resolver.AddMatch(found);
            }
        }

        #endregion

        #region /* RENDERING */

        public override void AsText(CodeWriter writer, RenderFlags flags)
        {
            if (flags.HasFlag(RenderFlags.Description))
                writer.Write(NamespaceDecl.ParseToken + " ");

            writer.Write(FullName);
        }

        #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 Common Development and Distribution License (CDDL)


Written By
Software Developer (Senior)
United States United States
I've been writing software since the late 70's, currently focusing mainly on C#.NET. I also like to travel around the world, and I own a Chocolate Factory (sadly, none of my employees are oompa loompas).

Comments and Discussions