Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Resolving Symbolic References in a CodeDOM (Part 7)

, 2 Dec 2012 CDDL
Resolving symbolic references in a CodeDOM.
Nova.0.6.exe.zip
Mono.Cecil.dll
Nova.CLI.exe
Nova.CodeDOM.dll
Nova.Examples.exe
Nova.Studio.exe
Nova.Test.exe
Nova.UI.dll
Nova.0.6.zip
Nova.CLI
Properties
Nova.CodeDOM
CodeDOM
Annotations
Base
Comments
Base
DocComments
CodeRef
Base
List
Name
Base
Other
Simple
CompilerDirectives
Base
Conditionals
Base
Messages
Base
Pragmas
Base
Symbols
Base
Base
Interfaces
Expressions
AnonymousMethods
Base
Operators
Base
Binary
Arithmetic
Base
Assignment
Base
Bitwise
Base
Conditional
Relational
Base
Shift
Base
Other
Base
Unary
Base
Other
References
Base
GotoTargets
Base
Methods
Namespaces
Other
Properties
Types
Base
Variables
Base
Projects
Assemblies
Namespaces
References
Base
Statements
Base
Conditionals
Base
Exceptions
Generics
Constraints
Base
Iterators
Base
Jumps
Loops
Methods
OperatorDecls
Miscellaneous
Namespaces
Properties
Base
Events
Types
Base
Variables
Base
Parsing
Base
Properties
Rendering
Resolving
Utilities
Mono.Cecil
Reflection
Nova.Examples
Properties
Nova.Studio
Images
About.png
Configuration.png
EditCopy.png
EditCut.png
EditDelete.png
EditPaste.png
EditRedo.png
EditUndo.png
Error.png
Exit.png
FileNew.png
FileOpen.png
FileSave.png
FileSaveAll.png
FileSaveAs.png
Find.png
Help.png
Info.png
Logo.png
Options.png
Print.png
PrintPreview.png
Properties.png
Todo.png
Warning.png
Objects.ico
Properties
Settings.settings
Nova.Test
Properties
Nova.UI
CodeDOM
Annotations
Base
Comments
Base
DocComments
CodeRef
Base
List
Name
Base
Other
Simple
CompilerDirectives
Base
Conditionals
Base
Messages
Base
Pragmas
Base
Symbols
Base
Base
Expressions
AnonymousMethods
Base
Operators
Base
Binary
Arithmetic
Base
Assignment
Base
Bitwise
Base
Conditional
Relational
Base
Shift
Base
Other
Base
Unary
Base
Other
References
Base
GotoTargets
Base
Methods
Namespaces
Other
Properties
Types
Base
Variables
Base
Projects
Namespaces
References
Base
Statements
Base
Conditionals
Base
Exceptions
Generics
Constraints
Base
Iterators
Base
Jumps
Loops
Methods
OperatorDecls
Miscellaneous
Namespaces
Properties
Base
Events
Types
Base
Variables
Base
Properties
Resolving
Utilties
// 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 System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

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

namespace Nova.CodeDOM
{
    /// <summary>
    /// Represents a collection of <see cref="Project"/> objects that are logically grouped (and compiled) together.
    /// </summary>
    public class Solution : CodeObject, INamedCodeObject, IFile
    {
        #region /* CONSTANTS */

        public const string SolutionFileExtension = ".sln";
        public const string SolutionOptionsExtension = ".nuo";

        public const string SolutionItems = "Solution Items";
        public const string SolutionItemsProjectSection = "SolutionItems";
        public const string WebsitePropertiesProjectSection = "WebsiteProperties";
        public const string SourceCodeControlGlobalSection = "SourceCodeControl";
        public const string SolutionConfigurationPlatformsGlobalSection = "SolutionConfigurationPlatforms";
        public const string ProjectConfigurationPlatformsGlobalSection = "ProjectConfigurationPlatforms";
        public const string SolutionPropertiesGlobalSection = "SolutionProperties";
        public const string NestedProjectsGlobalSection = "NestedProjects";
        public const string HideSolutionNodeProperty = "HideSolutionNode";

        public const string PlatformDotNet = ".NET";
        public const string PlatformAnyCPU = "Any CPU";
        public const string PlatformMixed = "Mixed Platforms";

        protected const string VisualStudioSolutionFileHeader = "Microsoft Visual Studio Solution File";
        protected const string ProductReleasePrefix = "# ";
        protected const string ProjectStart = "Project(";
        protected const string ProjectEnd = "EndProject";
        protected const string ProjectSectionStart = "ProjectSection(";
        protected const string ProjectSectionEnd = "EndProjectSection";
        protected const string PreProject = "preProject";
        protected const string PostProject = "postProject";
        protected const string GlobalStart = "Global";
        protected const string GlobalEnd = "EndGlobal";
        protected const string GlobalSectionStart = "GlobalSection(";
        protected const string GlobalSectionEnd = "EndGlobalSection";
        protected const string PreSolution = "preSolution";
        protected const string PostSolution = "postSolution";

        #endregion

        #region /* FIELDS */

        /// <summary>
        /// The name of the <see cref="Solution"/>.
        /// </summary>
        protected string _name;

        /// <summary>
        /// True if the <see cref="Solution"/> is newly created and hasn't been saved yet.
        /// </summary>
        protected bool _isNew;

        /// <summary>
        /// The full file name.
        /// </summary>
        protected string _fileName;

        /// <summary>
        /// The version of the solution file.
        /// (11.00 = VS2010 = v10, 10.00 = VS2008 = v9, 9.00 = VS2005 = v8, 8.00 = VS2003 = v7.1, ? = VS2002 = v7)
        /// </summary>
        protected string _formatVersion;

        /// <summary>
        /// The product release.
        /// (Visual Studio 2010, Visual C# Express 2008, etc.)
        /// </summary>
        protected string _productRelease;

        /// <summary>
        /// The active configuration (usually 'Debug' or 'Release').
        /// </summary>
        protected string _activeConfiguration;

        /// <summary>
        /// The active platform (usually 'Any CPU', 'x86', or 'Mixed Platforms').
        /// </summary>
        protected string _activePlatform;

        /// <summary>
        /// All projects in this solution (will be sorted alphabetically).
        /// </summary>
        protected ChildList<Project> _projects;

        /// <summary>
        /// All projects in this solution, ordered with most-dependent first.
        /// </summary>
        protected List<Project> _projectsInDependentOrder = new List<Project>();

        /// <summary>
        /// Project entries (these represent the entries in the solution file).
        /// </summary>
        protected List<ProjectEntry> _projectEntries = new List<ProjectEntry>();

        /// <summary>
        /// Global sections (blocks of configuration data).
        /// </summary>
        protected List<GlobalSection> _globalSections = new List<GlobalSection>();

        /// <summary>
        /// All 'listed' annotations (<see cref="Message"/>s and special <see cref="Comment"/>s) in this <see cref="Solution"/>.
        /// </summary>
        protected ObservableCollection<CodeAnnotation> _codeAnnotations = new ObservableCollection<CodeAnnotation>();
        protected Dictionary<Annotation, CodeAnnotation> _codeAnnotationsDictionary = new Dictionary<Annotation, CodeAnnotation>();

        /// <summary>
        /// Callback used to indicate status changes to the UI while loading.
        /// </summary>
        protected Action<LoadStatus, CodeObject> _statusCallback;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a new <see cref="Solution"/> object.
        /// </summary>
        /// <remarks>
        /// To load an existing solution, use <see cref="Load(string, LoadOptions, Action{LoadStatus, CodeObject})"/>.
        /// </remarks>
        public Solution(string fileName, Action<LoadStatus, CodeObject> statusCallback)
        {
            _name = Path.GetFileNameWithoutExtension(fileName);
            _isNew = true;
            _fileName = fileName;
            if (!Path.HasExtension(fileName))
                _fileName += SolutionFileExtension;
            FileEncoding = Encoding.UTF8;  // Default to UTF8 encoding with a BOM
            FileHasUTF8BOM = true;
            _projects = new ChildList<Project>(this);
            _statusCallback = statusCallback;

            // Default to VS2010
            _formatVersion = "11.00";
            _productRelease = "Visual Studio 2010";

            _globalSections.Add(new GlobalSection(SolutionConfigurationPlatformsGlobalSection, true));
            _globalSections.Add(new GlobalSection(ProjectConfigurationPlatformsGlobalSection, false));
            _globalSections.Add(new GlobalSection(SolutionPropertiesGlobalSection, true, new KeyValuePair<string, string>(HideSolutionNodeProperty, "FALSE")));

            // Determine the active configuration and platform (either specified, from previously saved settings, or defaulted).
            // This is done here even though the Solution is new, because even if it's a dummy solution for a directly-loaded
            // project, we still want to be able to change the active configuration/platform and re-load it.
            DetermineActiveConfigurationAndPlatform();
        }

        /// <summary>
        /// Create a new <see cref="Solution"/> object.
        /// </summary>
        /// <remarks>
        /// To load an existing solution, use <see cref="Load(string, LoadOptions, Action{LoadStatus, CodeObject})"/>.
        /// </remarks>
        public Solution(string fileName)
            : this(fileName, null)
        { }

        #endregion

        #region /* STATIC CONSTRUCTOR */

        static Solution()
        {
            // Force a reference to CodeObject to trigger the loading of any config file if it hasn't been done yet
            ForceReference();
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The name of the <see cref="Solution"/> (does not include the file path or extension).
        /// </summary>
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        /// <summary>
        /// True if the <see cref="Solution"/> is newly created and hasn't been saved yet.
        /// </summary>
        public bool IsNew
        {
            get { return _isNew; }
        }

        /// <summary>
        /// The associated file name of the <see cref="Solution"/>.
        /// </summary>
        public string FileName
        {
            get { return _fileName; }
            set { _fileName = value; }
        }

        /// <summary>
        /// True if the associated file exists.
        /// </summary>
        public bool FileExists
        {
            get { return File.Exists(_fileName); }
        }

        /// <summary>
        /// The encoding of the file (normally UTF8).
        /// </summary>
        public Encoding FileEncoding { get; set; }

        /// <summary>
        /// True if the file has a UTF8 byte-order-mark.
        /// </summary>
        public bool FileHasUTF8BOM { get; set; }

        /// <summary>
        /// Always <c>true</c> for a <see cref="Solution"/>.
        /// </summary>
        public bool FileUsingTabs
        {
            get { return true; }
            set { throw new Exception("Solution files always use tabs!"); }
        }

        /// <summary>
        /// The version number of the solution file format.
        /// </summary>
        public string FormatVersion
        {
            get { return _formatVersion; }
        }

        /// <summary>
        /// The product release number.
        /// </summary>
        public string ProductRelease
        {
            get { return _productRelease; }
        }

        /// <summary>
        /// The active configuration (usually 'Debug' or 'Release').
        /// </summary>
        /// <remarks>
        /// After changing the active configuration and/or platform, the solution should be re-loaded
        /// so that files can be parsed according to any defined compiler directive symbols, and so
        /// any generated code-behind files can be loaded from the proper output directory.
        /// </remarks>
        public string ActiveConfiguration
        {
            get { return _activeConfiguration; }
            set { _activeConfiguration = value; }
        }

        /// <summary>
        /// The active platform (usually 'Any CPU', 'x86', or 'Mixed Platforms').
        /// </summary>
        /// <remarks>
        /// After changing the active configuration and/or platform, the solution should be re-loaded
        /// so that files can be parsed according to any defined compiler directive symbols, and so
        /// any generated code-behind files can be loaded from the proper output directory.
        /// </remarks>
        public string ActivePlatform
        {
            get { return _activePlatform; }
            set { _activePlatform = value; }
        }

        /// <summary>
        /// The child <see cref="Project"/>s.
        /// </summary>
        public ChildList<Project> Projects
        {
            get { return _projects; }
        }

        /// <summary>
        /// The child <see cref="Project"/>s in dependent order.
        /// </summary>
        public List<Project> ProjectsInDependentOrder
        {
            get { return _projectsInDependentOrder; }
        }

        /// <summary>
        /// The <see cref="ProjectEntry"/>s of the associated '.sln' file.
        /// </summary>
        public List<ProjectEntry> ProjectEntries
        {
            get { return _projectEntries; }
        }

        /// <summary>
        /// The <see cref="GlobalSection"/>s of the associated '.sln' file.
        /// </summary>
        public List<GlobalSection> GlobalSections
        {
            get { return _globalSections; }
        }

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

        /// <summary>
        /// All 'listed' annotations (<see cref="Message"/>s and special <see cref="Comment"/>s) for the entire <see cref="Solution"/>.
        /// </summary>
        public ObservableCollection<CodeAnnotation> CodeAnnotations
        {
            get { return _codeAnnotations; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Add a <see cref="Project"/> to the <see cref="Solution"/>, keeping the <see cref="Projects"/> collection sorted alphabetically.
        /// </summary>
        public void AddProject(Project project)
        {
            if (project != null)
            {
                // Insert the Project at the proper index according to its name.
                // Duplicate names shouldn't exist, but technically could, so just insert any duplicate matches following the existing one.
                int index = _projects.BinarySearch(project);
                _projects.Insert((index < 0 ? ~index : index + 1), project);

                // Add to the Solution's configuration platforms
                foreach (Project.Configuration configuration in project.Configurations)
                    AddProjectConfiguration(configuration);
            }
        }

        /// <summary>
        /// Add the configuration and platform from the specified <see cref="Project.Configuration"/>.
        /// </summary>
        public void AddProjectConfiguration(Project.Configuration configuration)
        {
            // Add to the Solution's configuration platform section
            string platform = (configuration.Platform ?? PlatformAnyCPU);
            if (platform == Project.PlatformAnyCPU)
                platform = PlatformAnyCPU;
            string configurationPlatform = configuration.Name + "|" + platform;
            FindGlobalSection(SolutionConfigurationPlatformsGlobalSection).AddKeyValue(configurationPlatform, configurationPlatform);

            // Add default mappings to the Project's configuration platform mappings
            GlobalSection globalSection = FindGlobalSection(ProjectConfigurationPlatformsGlobalSection);
            Project project = (Project)configuration.Parent;
            string projectGuid = project.ProjectGuid.ToString("B").ToUpper();
            string projectConfigurationPlatform = configuration.Name;
            if (project.TypeGuid != Project.SetupProjectType)
                projectConfigurationPlatform += "|" + platform;
            string prefix = projectGuid + "." + configurationPlatform;
            globalSection.AddKeyValue(prefix + ".ActiveCfg", projectConfigurationPlatform);
            if (platform != PlatformDotNet)
                globalSection.AddKeyValue(prefix + ".Build.0", projectConfigurationPlatform);
            if (project.ProjectTypeGuids != null && project.ProjectTypeGuids.Count > 0 && project.ProjectTypeGuids.Contains(Project.VisualDBToolsProjectType))
                globalSection.AddKeyValue(prefix + ".Deploy.0", projectConfigurationPlatform);
        }

        /// <summary>
        /// Create and add a new <see cref="Project"/> to the solution by filename.
        /// </summary>
        public Project CreateProject(string fileName)
        {
            Project project = new Project(fileName, this);
            AddProject(project);
            _projectEntries.Add(new ProjectEntry(Project.CSProjectType, project));
            return project;
        }

        /// <summary>
        /// Get any Solution folders for the specified Project.
        /// </summary>
        public string GetSolutionFolders(Project project)
        {
            string solutionFolders = null;
            GlobalSection nestedProjects = FindGlobalSection(NestedProjectsGlobalSection);
            if (nestedProjects != null)
            {
                Guid projectGuid = project.ProjectGuid;
                while (true)
                {
                    string parentGuid = nestedProjects.FindValue(projectGuid.ToString("B").ToUpper());
                    if (parentGuid == null)
                        break;
                    ProjectEntry parentProject = FindProjectEntry(Guid.Parse(parentGuid));
                    if (parentProject == null)
                        break;
                    solutionFolders = (string.IsNullOrEmpty(solutionFolders) ? parentProject.Name : parentProject.Name + "\\" + solutionFolders);
                    projectGuid = parentProject.Guid;
                }
            }
            return solutionFolders;
        }

        /// <summary>
        /// Update the dependency-ordered list of <see cref="Project"/>s.
        /// </summary>
        public void UpdateProjectDependencies()
        {
            // Update dependencies without creating a new collection
            _projectsInDependentOrder.Clear();
            foreach (Project project in _projects)
            {
                // Scan the dependent project list for the first project that depends upon the
                // one being added, and insert right before it.
                int i = 0;
                for ( ; i < _projectsInDependentOrder.Count; ++i)
                {
                    if (_projectsInDependentOrder[i].IsDependentOn(project))
                        break;
                }
                _projectsInDependentOrder.Insert(i, project);
            }
            //bool valid = ValidateDependencies();  // Debugging logic to verify dependency determination
        }

#if VALIDATE_DEPENDENCIES
        // Debugging logic to verify dependency determination
        private bool ValidateDependencies()
        {
            bool valid = true;
            for (int i = 0; i < _projectsInDependentOrder.Count; ++i)
            {
                Project project = _projectsInDependentOrder[i];
                foreach (Reference reference in project.References)
                {
                    if (reference is ProjectReference)
                    {
                        bool found = false;
                        ProjectReference referencedProject = ((ProjectReference)reference).ReferencedProject;
                        for (int j = 0; j < i; ++j)
                        {
                            if (_projectsInDependentOrder[j] == referencedProject)
                            {
                                found = true;
                                break;
                            }
                        }
                        if (!found)
                        {
                            valid = false;
                            break;
                        }
                    }
                }
            }
            return valid;
        }
#endif

        /// <summary>
        /// Resolve all project references and update project dependencies.
        /// </summary>
        public void ResolveProjectReferencesAndUpdateDependencies()
        {
            // Resolve project references
            foreach (Project project in _projects)
                project.ResolveReferences();

            // Update project dependencies (project references must be resolved first for this to work)
            UpdateProjectDependencies();
        }

        /// <summary>
        /// Load all referenced assemblies for all projects, then load all types from them.
        /// </summary>
        public void LoadProjectReferencedAssembliesAndTypes()
        {
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();

            // Load all referenced assemblies first, before loading any types.  This is necessary so that framework "reference
            // assemblies" are given priority over runtime assemblies in the GAC for directly-referenced assemblies.  It also
            // allows for any errors finding direct references to be reported (and displayed) very quickly.
            foreach (Project project in _projectsInDependentOrder)
                project.LoadReferencedAssemblies();
            Log.WriteLine("Loaded all referenced assemblies, elapsed time: " + stopWatch.Elapsed.TotalSeconds.ToString("N3"));

            stopWatch.Restart();

            // Don't ignore any errors while loading
            ApplicationContext.GetMasterInstance().IgnoreDuplicateLoadErrors = false;

            // Now, load all types
            Project firstProject = null;
            int typeCount = 0;
            foreach (Project project in _projectsInDependentOrder)
            {
                if (firstProject == null)
                    firstProject = project;
                typeCount += project.LoadTypesFromReferenceAssembliesInternal();
            }
            Log.WriteLine("Loaded types from all referenced assemblies (" + typeCount.ToString("N0") + " total types), elapsed time: " + stopWatch.Elapsed.TotalSeconds.ToString("N3"));

            // Once we're done loading, we want to ignore any load errors for already-failed loads
            // that might occur later when drilling down into the metadata.
            ApplicationContext.GetMasterInstance().IgnoreDuplicateLoadErrors = true;

            // Initialize all static TypeRefs for the first Project - this is NOT THREAD SAFE, and also these types should really
            // be project-specific instead of static, but they're much more convenient static, will work fine if all projects have the
            // same target platform, and only causes cosmetic display of wrong source types if they don't (they will still compare OK).
            TypeRef.InitializeTypeRefs(firstProject);
        }

        /// <summary>
        /// Find a project by name.
        /// </summary>
        public Project FindProject(string name)
        {
            return Enumerable.FirstOrDefault(_projects, delegate(Project project) { return StringUtil.NNEqualsIgnoreCase(project.Name, name); });
        }

        /// <summary>
        /// Find a project by its assembly name.
        /// </summary>
        public Project FindProjectByAssemblyName(string assemblyName)
        {
            return Enumerable.FirstOrDefault(_projects, delegate(Project project) { return StringUtil.NNEqualsIgnoreCase(project.AssemblyName, assemblyName); });
        }

        /// <summary>
        /// Find a project by its full file name.
        /// </summary>
        public Project FindProjectByFileName(string fullFileName)
        {
            return Enumerable.FirstOrDefault(_projects, delegate(Project project) { return StringUtil.NNEqualsIgnoreCase(project.FileName, fullFileName); });
        }

        /// <summary>
        /// Find a project by its GUID.
        /// </summary>
        public Project FindProject(Guid projectGuid)
        {
            return Enumerable.FirstOrDefault(_projects, delegate(Project project) { return project.ProjectGuid == projectGuid; });
        }

        /// <summary>
        /// Add a listed annotation to the <see cref="Solution"/>.
        /// </summary>
        public void AnnotationAdded(Annotation annotation, Project project, CodeUnit codeUnit, bool sendStatus)
        {
            try
            {
                CodeAnnotation codeAnnotation = new CodeAnnotation(annotation, project, codeUnit);
                // The following line has been seen to throw an exception when the LOST COMMENT logic in the Token finalizer is
                // triggered, complaining that this is executed during the processing of a CollectionChanged event on the same
                // collection, even though this situation couldn't be found in any thread in the debugger.  So, we wrap this
                // method in a try/catch and swallow any such (very-low frequency) event - we'll still get a Log message for
                // the lost comment in the Output window (lost comments are also only tracked in Debug builds).
                lock (this)
                {
                    _codeAnnotations.Add(codeAnnotation);
                    _codeAnnotationsDictionary.Add(annotation, codeAnnotation);
                }

                // Send a status change if loading and an annotation (message) is added to a file (solution, project, reference,
                // or code unit), so that the UI can indicate any errors in the file tree.
                if (sendStatus && _statusCallback != null)
                    _statusCallback(LoadStatus.ObjectAnnotated, annotation.Parent);
            }
            catch { }
        }

        /// <summary>
        /// Remove a listed annotation from the <see cref="Solution"/>.
        /// </summary>
        public void AnnotationRemoved(Annotation annotation)
        {
            lock (this)
            {
                CodeAnnotation codeAnnotation;
                if (_codeAnnotationsDictionary.TryGetValue(annotation, out codeAnnotation))
                {
                    _codeAnnotations.Remove(codeAnnotation);
                    _codeAnnotationsDictionary.Remove(annotation);
                }
            }
        }

        protected override void NotifyListedAnnotationAdded(Annotation annotation)
        {
            AnnotationAdded(annotation, null, null, true);
        }

        protected override void NotifyListedAnnotationRemoved(Annotation annotation)
        {
            AnnotationRemoved(annotation);
        }

        /// <summary>
        /// Log the specified text message with the specified severity level.
        /// </summary>
        public void LogMessage(string message, MessageSeverity severity, string toolTip)
        {
            string prefix = (severity == MessageSeverity.Error ? "ERROR: " : (severity == MessageSeverity.Warning ? "Warning: " : ""));
            Log.WriteLine(prefix + "Solution '" + _name + "': " + message, toolTip != null ? toolTip.TrimEnd() : null);
        }

        /// <summary>
        /// Log the specified text message with the specified severity level.
        /// </summary>
        public void LogMessage(string message, MessageSeverity severity)
        {
            LogMessage(message, severity, null);
        }

        /// <summary>
        /// Log the specified exception and message.
        /// </summary>
        public string LogException(Exception ex, string message)
        {
            return Log.Exception(ex, message + " solution '" + _name + "'");
        }

        /// <summary>
        /// Log the specified text message and also attach it as an annotation.
        /// </summary>
        public void LogAndAttachMessage(string message, MessageSeverity severity, MessageSource source, string toolTip)
        {
            LogMessage(message, severity, toolTip);
            AttachMessage(message, severity, source);
        }

        /// <summary>
        /// Log the specified text message and also attach it as an annotation.
        /// </summary>
        public void LogAndAttachMessage(string message, MessageSeverity severity, MessageSource source)
        {
            LogAndAttachMessage(message, severity, source, null);
        }

        /// <summary>
        /// Log the specified exception and message and also attach it as an annotation.
        /// </summary>
        public void LogAndAttachException(Exception ex, string message, MessageSource source)
        {
            message = LogException(ex, message);
            AttachMessage(message, MessageSeverity.Error, source);
        }

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

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

        /// <summary>
        /// Load the specified <see cref="Solution"/> file, including all child <see cref="Project"/>s and code files.
        /// </summary>
        /// <param name="fileName">The solution ('.sln') file.</param>
        /// <param name="activeConfiguration">The active configuration to use (usually 'Debug' or 'Release').</param>
        /// <param name="activePlatform">The active platform to use (such as 'Any CPU', 'x86', 'Mixed Platforms', etc.</param>
        /// <param name="loadOptions">Determines various optional processing.</param>
        /// <param name="statusCallback">Status callback for monitoring progress.</param>
        /// <returns>The resulting <see cref="Solution"/> object, or null if the file didn't exist.</returns>
        /// <remarks>
        /// A 'Complete' load of a Solution goes through the following steps:
        ///   - Parse the Solution file, creating a Solution object and a list of ProjectEntry objects.
        ///   - Parse each Project file, creating a Project object and loading the lists of References and code files (CodeUnits).
        ///   - Resolve all References for each project, and determine the project dependencies.
        ///   - Load all referenced assemblies for each project (in dependency order).
        ///   - Load all types from all referenced assemblies for each project (in dependency order).
        ///   - Parse all code files for each project (in dependency order), first loading all types from any referenced projects.
        ///   - Resolve all symbolic references in all code files for each project (in dependency order).
        /// The 'LoadOnly' option stops this process after parsing the Solution and Project files and resolving References, which
        /// is useful if only the solution and/or project files are being viewed, analyzed, or edited.  The 'DoNotResolve' option
        /// skips resolving symbolic references, which is useful if that step is not required, such as when only formatting code
        /// which doesn't rely on resolved references.
        /// </remarks>
        public static Solution Load(string fileName, string activeConfiguration, string activePlatform, LoadOptions loadOptions, Action<LoadStatus, CodeObject> statusCallback)
        {
            Solution solution = null;
            try
            {
                if (statusCallback != null)
                    statusCallback(LoadStatus.Loading, null);
                Stopwatch overallStopWatch = new Stopwatch();
                overallStopWatch.Start();
                GC.Collect();
                long startBytes = GC.GetTotalMemory(true);

                // Handle a relative path to the file
                if (!Path.IsPathRooted(fileName))
                    fileName = FileUtil.CombineAndNormalizePath(Environment.CurrentDirectory, fileName);

                // Parse and resolve the solution, projects, and code units
                Unrecognized.Count = 0;
                solution = Parse(fileName, activeConfiguration, activePlatform, statusCallback);
                if (solution != null)
                {
                    solution.ResolveProjectReferencesAndUpdateDependencies();
                    Log.WriteLine("Loaded all projects, elapsed time: " + overallStopWatch.Elapsed.TotalSeconds.ToString("N3"));
                    if (statusCallback != null)
                        statusCallback(LoadStatus.ProjectsLoaded, null);
                    if (loadOptions.HasFlag(LoadOptions.ResolveSources))
                        solution.LoadProjectReferencedAssembliesAndTypes();
                    Log.WriteLine("Loaded solution '" + solution.Name + "', total elapsed time: " + overallStopWatch.Elapsed.TotalSeconds.ToString("N3"));

                    if (loadOptions.HasFlag(LoadOptions.ParseSources) || loadOptions.HasFlag(LoadOptions.ResolveSources))
                    {
                        if (statusCallback != null)
                            statusCallback(LoadStatus.Parsing, null);
                        Stopwatch stopWatch = new Stopwatch();
                        stopWatch.Start();
                        ParseFlags parseFlags = (loadOptions.HasFlag(LoadOptions.DoNotParseBodies) ? ParseFlags.SkipMethodBodies : ParseFlags.None);
                        foreach (Project project in solution.ProjectsInDependentOrder)
                        {
                            if (loadOptions.HasFlag(LoadOptions.ResolveSources))
                                project.LoadTypesFromReferencedProjects();
                            project.ParseCodeUnits(parseFlags);
                        }
                        if (Unrecognized.Count > 0)
                            Log.WriteLine("UNRECOGNIZED OBJECT COUNT: " + Unrecognized.Count);
                        Log.WriteLine("Parsed solution '" + solution.Name + "', elapsed time: " + stopWatch.Elapsed.TotalSeconds.ToString("N3"));

                        if (loadOptions.HasFlag(LoadOptions.ResolveSources))
                        {
                            if (statusCallback != null)
                                statusCallback(LoadStatus.Resolving, null);
                            stopWatch.Restart();
                            Resolver.ResolveAttempts = Resolver.ResolveFailures = 0;
                            ResolveFlags resolveFlags = (loadOptions.HasFlag(LoadOptions.DoNotResolveBodies) ? ResolveFlags.SkipMethodBodies : ResolveFlags.None);

                            // Do a 3-phase resolve to prevent failures due to dependencies while essentially being a single pass
                            foreach (Project project in solution.ProjectsInDependentOrder)
                                project.ResolveCodeUnits(resolveFlags | ResolveFlags.Phase1);
                            foreach (Project project in solution.ProjectsInDependentOrder)
                                project.ResolveCodeUnits(resolveFlags | ResolveFlags.Phase2);
                            foreach (Project project in solution.ProjectsInDependentOrder)
                                project.ResolveCodeUnits(resolveFlags | ResolveFlags.Phase3);

                            Log.WriteLine(string.Format("Resolved solution '{0}', elapsed time: {1:N3}, ResolveAttempts = {2:N0}, ResolveFailures = {3:N0}",
                                solution.Name, stopWatch.Elapsed.TotalSeconds, Resolver.ResolveAttempts, Resolver.ResolveFailures));
                        }
                    }

                    long memoryUsage = GC.GetTotalMemory(true) - startBytes;
                    Log.WriteLine(string.Format("Total elapsed time: {0:N3}, memory usage: {1} MBs", overallStopWatch.Elapsed.TotalSeconds, memoryUsage / (1024 * 1024)));

                    if (statusCallback != null)
                        statusCallback(LoadStatus.LoggingResults, null);
                    solution.LogMessageCounts(loadOptions.HasFlag(LoadOptions.LogMessages));

                    solution._statusCallback = null;
                }
            }
            catch (Exception ex)
            {
                Log.Exception(ex, "loading solution");
            }
            return solution;
        }

        /// <summary>
        /// Load the specified <see cref="Solution"/> file, including all child <see cref="Project"/>s and code files.
        /// </summary>
        /// <param name="fileName">The solution ('.sln') file.</param>
        /// <param name="activeConfiguration">The active configuration to use (usually 'Debug' or 'Release').</param>
        /// <param name="activePlatform">The active platform to use (such as 'Any CPU', 'x86', 'Mixed Platforms', etc.</param>
        /// <param name="loadOptions">Determines various optional processing.</param>
        /// <returns>The resulting <see cref="Solution"/> object, or null if the file didn't exist.</returns>
        /// <remarks>
        /// A 'Complete' load of a Solution goes through the following steps:
        ///   - Parse the Solution file, creating a Solution object and a list of ProjectEntry objects.
        ///   - Parse each Project file, creating a Project object and loading the lists of References and code files (CodeUnits).
        ///   - Resolve all References for each project, and determine the project dependencies.
        ///   - Load all referenced assemblies for each project (in dependency order).
        ///   - Load all types from all referenced assemblies for each project (in dependency order).
        ///   - Parse all code files for each project (in dependency order), first loading all types from any referenced projects.
        ///   - Resolve all symbolic references in all code files for each project (in dependency order).
        /// The 'LoadOnly' option stops this process after parsing the Solution and Project files and resolving References, which
        /// is useful if only the solution and/or project files are being viewed, analyzed, or edited.  The 'DoNotResolve' option
        /// skips resolving symbolic references, which is useful if that step is not required, such as when only formatting code
        /// which doesn't rely on resolved references.
        /// </remarks>
        public static Solution Load(string fileName, string activeConfiguration, string activePlatform, LoadOptions loadOptions)
        {
            return Load(fileName, activeConfiguration, activePlatform, loadOptions, null);
        }

        /// <summary>
        /// Load the specified <see cref="Solution"/> file, including all child <see cref="Project"/>s and code files.
        /// </summary>
        /// <param name="fileName">The solution ('.sln') file.</param>
        /// <param name="activeConfiguration">The active configuration to use (usually 'Debug' or 'Release').</param>
        /// <param name="activePlatform">The active platform to use (such as 'Any CPU', 'x86', 'Mixed Platforms', etc.</param>
        /// <returns>The resulting <see cref="Solution"/> object, or null if the file didn't exist.</returns>
        /// <remarks>
        /// A 'Complete' load of a Solution goes through the following steps:
        ///   - Parse the Solution file, creating a Solution object and a list of ProjectEntry objects.
        ///   - Parse each Project file, creating a Project object and loading the lists of References and code files (CodeUnits).
        ///   - Resolve all References for each project, and determine the project dependencies.
        ///   - Load all referenced assemblies for each project (in dependency order).
        ///   - Load all types from all referenced assemblies for each project (in dependency order).
        ///   - Parse all code files for each project (in dependency order), first loading all types from any referenced projects.
        ///   - Resolve all symbolic references in all code files for each project (in dependency order).
        /// The 'LoadOnly' option stops this process after parsing the Solution and Project files and resolving References, which
        /// is useful if only the solution and/or project files are being viewed, analyzed, or edited.  The 'DoNotResolve' option
        /// skips resolving symbolic references, which is useful if that step is not required, such as when only formatting code
        /// which doesn't rely on resolved references.
        /// </remarks>
        public static Solution Load(string fileName, string activeConfiguration, string activePlatform)
        {
            return Load(fileName, activeConfiguration, activePlatform, LoadOptions.Complete, null);
        }

        /// <summary>
        /// Load the specified <see cref="Solution"/> file, including all child <see cref="Project"/>s and code files.
        /// </summary>
        /// <param name="fileName">The solution ('.sln') file.</param>
        /// <param name="activeConfiguration">The active configuration to use (usually 'Debug' or 'Release').</param>
        /// <returns>The resulting <see cref="Solution"/> object, or null if the file didn't exist.</returns>
        /// <remarks>
        /// A 'Complete' load of a Solution goes through the following steps:
        ///   - Parse the Solution file, creating a Solution object and a list of ProjectEntry objects.
        ///   - Parse each Project file, creating a Project object and loading the lists of References and code files (CodeUnits).
        ///   - Resolve all References for each project, and determine the project dependencies.
        ///   - Load all referenced assemblies for each project (in dependency order).
        ///   - Load all types from all referenced assemblies for each project (in dependency order).
        ///   - Parse all code files for each project (in dependency order), first loading all types from any referenced projects.
        ///   - Resolve all symbolic references in all code files for each project (in dependency order).
        /// The 'LoadOnly' option stops this process after parsing the Solution and Project files and resolving References, which
        /// is useful if only the solution and/or project files are being viewed, analyzed, or edited.  The 'DoNotResolve' option
        /// skips resolving symbolic references, which is useful if that step is not required, such as when only formatting code
        /// which doesn't rely on resolved references.
        /// </remarks>
        public static Solution Load(string fileName, string activeConfiguration)
        {
            return Load(fileName, activeConfiguration, null, LoadOptions.Complete, null);
        }

        /// <summary>
        /// Load the specified <see cref="Solution"/> file, including all child <see cref="Project"/>s and code files.
        /// </summary>
        /// <param name="fileName">The solution (".sln") file.</param>
        /// <param name="loadOptions">Determines various optional processing.</param>
        /// <param name="statusCallback">Status callback for monitoring progress.</param>
        /// <returns>The resulting <see cref="Solution"/> object, or null if the file didn't exist.</returns>
        /// <remarks>
        /// A 'Complete' load of a Solution goes through the following steps:
        ///   - Parse the Solution file, creating a Solution object and a list of ProjectEntry objects.
        ///   - Parse each Project file, creating a Project object and loading the lists of References and code files (CodeUnits).
        ///   - Resolve all References for each project, and determine the project dependencies.
        ///   - Load all referenced assemblies for each project (in dependency order).
        ///   - Load all types from all referenced assemblies for each project (in dependency order).
        ///   - Parse all code files for each project (in dependency order), first loading all types from any referenced projects.
        ///   - Resolve all symbolic references in all code files for each project (in dependency order).
        /// The 'LoadOnly' option stops this process after parsing the Solution and Project files and resolving References, which
        /// is useful if only the solution and/or project files are being viewed, analyzed, or edited.  The 'DoNotResolve' option
        /// skips resolving symbolic references, which is useful if that step is not required, such as when only formatting code
        /// which doesn't rely on resolved references.
        /// </remarks>
        public static Solution Load(string fileName, LoadOptions loadOptions, Action<LoadStatus, CodeObject> statusCallback)
        {
            return Load(fileName, null, null, loadOptions, statusCallback);
        }

        /// <summary>
        /// Load the specified <see cref="Solution"/> file, including all child <see cref="Project"/>s and code files.
        /// </summary>
        /// <param name="fileName">The solution (".sln") file.</param>
        /// <param name="loadOptions">Determines various optional processing.</param>
        /// <returns>The resulting <see cref="Solution"/> object, or null if the file didn't exist.</returns>
        /// <remarks>
        /// A 'Complete' load of a Solution goes through the following steps:
        ///   - Parse the Solution file, creating a Solution object and a list of ProjectEntry objects.
        ///   - Parse each Project file, creating a Project object and loading the lists of References and code files (CodeUnits).
        ///   - Resolve all References for each project, and determine the project dependencies.
        ///   - Load all referenced assemblies for each project (in dependency order).
        ///   - Load all types from all referenced assemblies for each project (in dependency order).
        ///   - Parse all code files for each project (in dependency order), first loading all types from any referenced projects.
        ///   - Resolve all symbolic references in all code files for each project (in dependency order).
        /// The 'LoadOnly' option stops this process after parsing the Solution and Project files and resolving References, which
        /// is useful if only the solution and/or project files are being viewed, analyzed, or edited.  The 'DoNotResolve' option
        /// skips resolving symbolic references, which is useful if that step is not required, such as when only formatting code
        /// which doesn't rely on resolved references.
        /// </remarks>
        public static Solution Load(string fileName, LoadOptions loadOptions)
        {
            return Load(fileName, null, null, loadOptions, null);
        }

        /// <summary>
        /// Load the specified <see cref="Solution"/> file, including all child <see cref="Project"/>s and code files.
        /// </summary>
        /// <param name="fileName">The solution (".sln") file.</param>
        /// <returns>The resulting <see cref="Solution"/> object, or null if the file didn't exist.</returns>
        /// <remarks>
        /// A 'Complete' load of a Solution goes through the following steps:
        ///   - Parse the Solution file, creating a Solution object and a list of ProjectEntry objects.
        ///   - Parse each Project file, creating a Project object and loading the lists of References and code files (CodeUnits).
        ///   - Resolve all References for each project, and determine the project dependencies.
        ///   - Load all referenced assemblies for each project (in dependency order).
        ///   - Load all types from all referenced assemblies for each project (in dependency order).
        ///   - Parse all code files for each project (in dependency order), first loading all types from any referenced projects.
        ///   - Resolve all symbolic references in all code files for each project (in dependency order).
        /// The 'LoadOnly' option stops this process after parsing the Solution and Project files and resolving References, which
        /// is useful if only the solution and/or project files are being viewed, analyzed, or edited.  The 'DoNotResolve' option
        /// skips resolving symbolic references, which is useful if that step is not required, such as when only formatting code
        /// which doesn't rely on resolved references.
        /// </remarks>
        public static Solution Load(string fileName)
        {
            return Load(fileName, null, null, LoadOptions.Complete, null);
        }

        /// <summary>
        /// Calculate message counts.
        /// </summary>
        public void GetMessageCounts(out int errorCount, out int warningCount, out int commentCount)
        {
            // Calculate message counts
            errorCount = warningCount = commentCount = 0;
            foreach (CodeAnnotation codeAnnotation in _codeAnnotations)
            {
                if (codeAnnotation.Annotation is Message)
                {
                    Message message = (Message)codeAnnotation.Annotation;
                    if (message.Severity == MessageSeverity.Error)
                        ++errorCount;
                    else if (message.Severity == MessageSeverity.Warning)
                        ++warningCount;
                }
                else if (codeAnnotation.Annotation is Comment)
                    ++commentCount;
            }
        }

        /// <summary>
        /// Log message counts, and optionally errors and warnings (or all messages if detail logging is on).
        /// </summary>
        public void LogMessageCounts(bool logMessages)
        {
            // Calculate and log message counts
            int errorCount, warningCount, commentCount;
            GetMessageCounts(out errorCount, out warningCount, out commentCount);
            Log.WriteLine(string.Format("{0:N0} messages ({1:N0} errors; {2:N0} warnings; {3:N0} comments)", _codeAnnotations.Count, errorCount, warningCount, commentCount));

            // Log errors and warnings if requested
            if (logMessages)
            {
                foreach (CodeAnnotation codeAnnotation in _codeAnnotations)
                {
                    // Log all messages if the LogLevel is Detailed, log Warnings if Normal, and Errors if Minimal
                    Message message = codeAnnotation.Annotation as Message;
                    if (Log.LogLevel >= Log.Level.Detailed || (message != null
                        && ((Log.LogLevel >= Log.Level.Normal && message.Severity == MessageSeverity.Warning)
                        || (Log.LogLevel >= Log.Level.Minimal && message.Severity == MessageSeverity.Error))))
                        Log.WriteLine(codeAnnotation.ToString());
                }
            }
        }

        /// <summary>
        /// Unload the <see cref="Solution"/> - unload all <see cref="Project"/>s, <see cref="FrameworkContext"/>s, the master <see cref="ApplicationContext"/>
        /// instance, clear all <see cref="CodeAnnotation"/>s, etc.
        /// </summary>
        public void Unload()
        {
            lock (this)
            {
                _codeAnnotations.Clear();
                _codeAnnotationsDictionary.Clear();
                TypeRef.ClearStaticReferences();  // Clear all static references to release any Mono Cecil types
                foreach (Project project in _projects)
                    project.Unload();
                ApplicationContext.GetMasterInstance().Unload();
            }
        }

        /// <summary>
        /// Save the <see cref="Solution"/> to the specified file name.
        /// </summary>
        public void SaveAs(string fileName)
        {
            Log.WriteLine("Saving solution to '" + fileName + "' ...");

            // VS solution files use tabs, but this is handled by the rendering routines.
            try
            {
                using (CodeWriter writer = new CodeWriter(fileName, FileEncoding, FileHasUTF8BOM, false))
                    AsText(writer, RenderFlags.None);
            }
            catch (Exception ex)
            {
                LogException(ex, "writing");
            }
            _isNew = false;
        }

        /// <summary>
        /// Save the <see cref="Solution"/>.
        /// </summary>
        public void Save()
        {
            SaveAs(CodeUnit.GetSaveFileName(_fileName));
        }

        /// <summary>
        /// Save the <see cref="Solution"/> plus all <see cref="Project"/>s and all <see cref="CodeUnit"/>s.
        /// </summary>
        public void SaveAll()
        {
            Save();
            foreach (Project project in Projects)
                project.SaveAll();
        }

        /// <summary>
        /// Find any ProjectEntry with the specified name.
        /// </summary>
        public ProjectEntry FindProjectEntry(string name)
        {
            return Enumerable.FirstOrDefault(_projectEntries, delegate(ProjectEntry projectEntry) { return projectEntry.Name == name; });
        }

        /// <summary>
        /// Find any ProjectEntry with the specified Guid.
        /// </summary>
        public ProjectEntry FindProjectEntry(Guid guid)
        {
            return Enumerable.FirstOrDefault(_projectEntries, delegate(ProjectEntry projectEntry) { return projectEntry.Guid == guid; });
        }

        /// <summary>
        /// Find any GlobalSection with the specified name.
        /// </summary>
        public GlobalSection FindGlobalSection(string name)
        {
            return Enumerable.FirstOrDefault(_globalSections, delegate(GlobalSection globalSection) { return globalSection.Name == name; });
        }

        /// <summary>
        /// Get the directory for the specified web site project.
        /// </summary>
        public string GetWebSiteDirectory(string projectName)
        {
            ProjectEntry projectEntry = FindProjectEntry(projectName);
            if (projectEntry != null)
            {
                string relativePath = null;
                if (projectEntry.FileName.StartsWith("http:"))
                {
                    ProjectSection projectSection = projectEntry.FindProjectSection(WebsitePropertiesProjectSection);
                    if (projectSection != null)
                        relativePath = projectSection.FindValue("SlnRelativePath").Trim('"');
                }
                else
                    relativePath = projectEntry.FileName;
                if (relativePath != null)
                    return Path.Combine(Path.GetDirectoryName(FileName) ?? "", relativePath.TrimEnd('\\'));
            }
            return null;
        }

        /// <summary>
        /// Determine lists of unique configurations and platforms from the appropriate global section.
        /// </summary>
        /// <param name="configurations">The list of configurations.</param>
        /// <param name="platforms">The list of platforms.</param>
        public void GetConfigurationsAndPlatforms(out List<string> configurations, out List<string> platforms)
        {
            configurations = null;
            platforms = null;
            GlobalSection globalSection = FindGlobalSection(SolutionConfigurationPlatformsGlobalSection);
            List<string> uniqueConfigurations = new List<string>();
            List<string> uniquePlatforms = new List<string>();
            foreach (KeyValuePair<string, string> keyValuePair in globalSection.KeyValuePairs)
            {
                string[] parts = keyValuePair.Key.Split('|');
                if (!uniqueConfigurations.Contains(parts[0]))
                    uniqueConfigurations.Add(parts[0]);
                if (parts.Length > 1 && !uniquePlatforms.Contains(parts[1]))
                    uniquePlatforms.Add(parts[1]);
            }
            if (uniqueConfigurations.Count > 0)
                configurations = uniqueConfigurations;
            if (uniquePlatforms.Count > 0)
                platforms = uniquePlatforms;
        }

        /// <summary>
        /// Get the configuration and platform for the specified project for the given solution configuration and platform.
        /// </summary>
        /// <param name="solutionConfiguration">The solution configuration.</param>
        /// <param name="solutionPlatform">The solution platform.</param>
        /// <param name="project">The <see cref="Project"/>.</param>
        /// <param name="projectConfiguration">The configuration of the <see cref="Project"/>.</param>
        /// <param name="projectPlatform">The platform of the <see cref="Project"/> (can be null).</param>
        public void GetProjectConfiguration(string solutionConfiguration, string solutionPlatform, Project project, out string projectConfiguration, out string projectPlatform)
        {
            projectConfiguration = null;
            projectPlatform = null;
            GlobalSection globalSection = FindGlobalSection(ProjectConfigurationPlatformsGlobalSection);
            if (globalSection != null)
            {
                string result = globalSection.FindValue(project.ProjectGuid.ToString("B").ToUpper() + "." + solutionConfiguration + "|" + solutionPlatform + ".ActiveCfg");
                // If no match was found, and the platform wasn't "Any CPU", then try that
                if (result == null && solutionPlatform != PlatformAnyCPU)
                    result = globalSection.FindValue(project.ProjectGuid.ToString("B").ToUpper() + "." + solutionConfiguration + "|" + PlatformAnyCPU + ".ActiveCfg");
                if (result != null)
                {
                    string[] parts = result.Split('|');
                    projectConfiguration = parts[0];
                    if (parts.Length > 1)
                    {
                        projectPlatform = parts[1];
                        if (projectPlatform == PlatformAnyCPU)
                            projectPlatform = Project.PlatformAnyCPU;
                    }
                }
            }
        }

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

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

        #endregion

        #region /* PARSING */

        /// <summary>
        /// Parse a solution from a file.
        /// </summary>
        /// <param name="fileName">The solution file name.</param>
        /// <param name="activeConfiguration">The active configuration to be used (will default if null).</param>
        /// <param name="activePlatform">The active platform to be used (will default if null).</param>
        /// <param name="statusCallback">An action to be executed as status changes occur during processing.</param>
        public static Solution Parse(string fileName, string activeConfiguration, string activePlatform, Action<LoadStatus, CodeObject> statusCallback)
        {
            if (File.Exists(fileName))
                return new Solution(fileName, activeConfiguration, activePlatform, statusCallback);

            Log.WriteLine("ERROR: Solution file '" + fileName + "' does not exist.");
            return null;
        }

        /// <summary>
        /// Parse a solution from a file.
        /// </summary>
        /// <param name="fileName">The solution file name.</param>
        /// <param name="activeConfiguration">The active configuration to be used (will default if null).</param>
        /// <param name="activePlatform">The active platform to be used (will default if null).</param>
        public static Solution Parse(string fileName, string activeConfiguration, string activePlatform)
        {
            return Parse(fileName, activeConfiguration, activePlatform, null);
        }

        /// <summary>
        /// Parse a solution from a file.
        /// </summary>
        /// <param name="fileName">The solution file name.</param>
        /// <param name="activeConfiguration">The active configuration to be used (will default if null).</param>
        public static Solution Parse(string fileName, string activeConfiguration)
        {
            return Parse(fileName, activeConfiguration, null, null);
        }

        /// <summary>
        /// Parse a solution from a file.
        /// </summary>
        /// <param name="fileName">The solution file name.</param>
        public static Solution Parse(string fileName)
        {
            return Parse(fileName, null, null, null);
        }

        /// <summary>
        /// Parse a solution from a standard VS solution file.
        /// </summary>
        /// <param name="fileName">The solution file name.</param>
        /// <param name="activeConfiguration">The active configuration to be used (will default if null).</param>
        /// <param name="activePlatform">The active platform to be used (will default if null).</param>
        /// <param name="statusCallback">An action to be executed as status changes occur during processing.</param>
        protected Solution(string fileName, string activeConfiguration, string activePlatform, Action<LoadStatus, CodeObject> statusCallback)
        {
            _name = Path.GetFileNameWithoutExtension(fileName) ?? fileName;
            Log.WriteLine("Loading solution '" + _name + "' ...");
            _fileName = fileName;
            _activeConfiguration = activeConfiguration;
            _activePlatform = activePlatform;
            _projects = new ChildList<Project>(this);
            _statusCallback = statusCallback;
            if (statusCallback != null)
                statusCallback(LoadStatus.ObjectCreated, this);

            try
            {
                // Open the file and store the encoding and BOM status for use when saving
                FileStream fileStream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read);
                byte[] bom = new byte[3];
                fileStream.Read(bom, 0, 3);
                if (bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF)
                    FileHasUTF8BOM = true;
                fileStream.Position = 0;

                // Parse the file using a StreamReader
                using (StreamReader reader = new StreamReader(fileStream))
                {
                    reader.Peek();  // Peek at the first char so that the encoding is determined
                    FileEncoding = reader.CurrentEncoding;

                    // Parse projects and other settings from the solution file
                    bool lookingForHeader = true;
                    do
                    {
                        string line = ReadLine(reader);
                        if (line == null) break;
                        if (lookingForHeader)
                        {
                            if (string.IsNullOrEmpty(line))
                                continue;

                            // Validate the file format
                            if (!line.StartsWith(VisualStudioSolutionFileHeader))
                            {
                                LogAndAttachMessage("Solution file format not recognized!", MessageSeverity.Error, MessageSource.Parse);
                                return;
                            }

                            // Extract the format version number
                            _formatVersion = line.Substring(line.LastIndexOf(' ') + 1);

                            lookingForHeader = false;
                            continue;
                        }

                        // Check for product release comment
                        if (line.StartsWith(ProductReleasePrefix))
                            _productRelease = line.Substring(ProductReleasePrefix.Length);
                        // Check for a project entry
                        else if (line.StartsWith(ProjectStart))
                            ParseProject(reader, line);
                        // Check for a global section
                        else if (line.StartsWith(GlobalSectionStart))
                        {
                            GlobalSection globalSection = new GlobalSection(reader, line, this);
                            _globalSections.Add(globalSection);
                        }
                        else if (line == GlobalStart || line == GlobalEnd)
                        {
                            // Just ignore these lines
                        }
                        else if (!string.IsNullOrEmpty(line))
                            UnrecognizedLine(line);
                    }
                    while (true);
                }

                // Determine the active configuration and platform (either specified, from previously saved settings, or defaulted).
                DetermineActiveConfigurationAndPlatform();
                Log.WriteLine("Active Configuration and Platform: " + _activeConfiguration + " - " + _activePlatform);

                if (statusCallback != null)
                    statusCallback(LoadStatus.SolutionLoaded, null);

                // Now that we've parsed all of the ProjectEntries, create the Project objects (this is done separately so that
                // the UI can know how many projects there are before they start getting created).
                foreach (ProjectEntry projectEntry in _projectEntries)
                {
                    if (!projectEntry.IsFolder)
                    {
                        string projectFileName = projectEntry.FileName;
                        if (projectFileName != null && !projectFileName.StartsWith("http:") && !Path.IsPathRooted(projectFileName))
                            projectFileName = FileUtil.CombineAndNormalizePath(Path.GetDirectoryName(_fileName), projectFileName);
                        Project project = Project.Parse(projectEntry.Name, projectFileName, projectEntry.TypeGuid, projectEntry.Guid, this, statusCallback);
                        AddProject(project);
                        if (statusCallback != null)
                            statusCallback(LoadStatus.ProjectParsed, project);
                        projectEntry.Project = project;
                    }
                }
            }
            catch (Exception ex)
            {
                LogAndAttachException(ex, "parsing", MessageSource.Parse);
            }
        }

        /// <summary>
        /// Parse a project.
        /// </summary>
        protected void ParseProject(StreamReader reader, string line)
        {
            try
            {
                int start = ProjectStart.Length;
                int end = line.IndexOf(')', start);
                if (end > 0)
                {
                    // Parse the ProjectEntry object
                    Guid typeGuid = Guid.Parse(line.Substring(start, end - start).Trim().Trim('"'));
                    string[] args = line.Substring(end + 3).Split(',');
                    string projectName = (args.Length > 0 ? args[0].Trim().Trim('"') : null);
                    string projectFileName = (args.Length > 1 ? args[1].Trim().Trim('"') : null);
                    Guid projectGuid = Guid.Parse(args.Length > 2 ? args[2].Trim().Trim('"') : "");
                    ProjectEntry projectEntry = new ProjectEntry(reader, typeGuid, projectName, projectFileName, projectGuid, this);
                    _projectEntries.Add(projectEntry);
                }
            }
            catch (Exception ex)
            {
                LogAndAttachException(ex, "parsing Project entry for", MessageSource.Parse);
            }
        }

        protected void UnrecognizedLine(string line)
        {
            LogAndAttachMessage("Unrecognized line while parsing: '" + line + "'", MessageSeverity.Error, MessageSource.Parse);
        }

        protected string ReadLine(StreamReader reader)
        {
            string line = reader.ReadLine();
            if (!string.IsNullOrEmpty(line))
                line = line.Trim();
            return line;
        }

        /// <summary>
        /// Determine the active configuration and platform (either from previously saved settings, or defaults).
        /// </summary>
        protected void DetermineActiveConfigurationAndPlatform()
        {
            if (_activeConfiguration == null)
            {
                // First, create lists of valid configurations and platforms from the appropriate global section (we can't build it
                // from the project files, because we have to do this before we read them, so we have to rely on the global section in
                // the solution file being correct).
                GlobalSection globalSection = FindGlobalSection(SolutionConfigurationPlatformsGlobalSection);
                List<string> uniqueConfigurations = new List<string>();
                List<string> uniquePlatforms = new List<string>();
                foreach (KeyValuePair<string, string> keyValuePair in globalSection.KeyValuePairs)
                {
                    string[] parts = keyValuePair.Key.Split('|');
                    if (!uniqueConfigurations.Contains(parts[0]))
                        uniqueConfigurations.Add(parts[0]);
                    if (parts.Length > 1 && !uniquePlatforms.Contains(parts[1]))
                        uniquePlatforms.Add(parts[1]);
                }

                // Try to read any previously saved active configuration and platform from the '.nuo' settings file (if any).
                LoadUserOptions();

                // If there weren't any saved settings or they weren't valid, default the configuration to 'Debug' or whatever is first,
                // and default the platform to 'Any CPU' or whatever is first.
                if (_activeConfiguration == null && uniqueConfigurations.Count > 0)
                {
                    if (uniqueConfigurations.Contains(Project.ConfigurationDebug))
                        _activeConfiguration = Project.ConfigurationDebug;
                    else
                        _activeConfiguration = uniqueConfigurations[0];
                }
                if (_activePlatform == null && uniquePlatforms.Count > 0)
                {
                    if (uniquePlatforms.Contains(PlatformAnyCPU))
                        _activePlatform = PlatformAnyCPU;
                    else
                        _activePlatform = uniquePlatforms[0];
                }
            }
        }

        /// <summary>
        /// Save user options (such as active configuration/platform) to a '.nuo' file.
        /// </summary>
        public void SaveUserOptions()
        {
            try
            {
                using (XmlTextWriter xmlWriter = new XmlTextWriter(Path.ChangeExtension(FileName, SolutionOptionsExtension), Encoding.ASCII))
                {
                    xmlWriter.Formatting = Formatting.Indented;
                    xmlWriter.WriteStartDocument();
                    xmlWriter.WriteStartElement("NovaSolutionOptions");
                    xmlWriter.WriteAttributeString("Version", "1");
                    xmlWriter.WriteElementString("ActiveConfiguration", ActiveConfiguration);
                    xmlWriter.WriteElementString("ActivePlatform", ActivePlatform);
                    xmlWriter.WriteEndDocument();
                }
            }
            catch { }
        }

        /// <summary>
        /// Load user options (such as active configuration/platform) from any '.nuo' file.
        /// </summary>
        public void LoadUserOptions()
        {
            try
            {
                if (File.Exists(FileName))
                {
                    using (XmlTextReader xmlReader = new XmlTextReader(Path.ChangeExtension(FileName, SolutionOptionsExtension)))
                    {
                        bool firstElement = true;
                        //int version = 0;

                        // Read the next node
                        while (xmlReader.Read())
                        {
                            if (xmlReader.NodeType == XmlNodeType.Element)
                            {
                                if (firstElement)
                                {
                                    firstElement = false;
                                    if (xmlReader.Name != "NovaSolutionOptions")
                                        return;
                                    //if (xmlReader.MoveToAttribute("Version"))
                                    //    version = xmlReader.Value.ParseInt();
                                }
                                else if (xmlReader.Name == "ActiveConfiguration" && !xmlReader.IsEmptyElement)
                                    ActiveConfiguration = xmlReader.ReadString().Trim();
                                else if (xmlReader.Name == "ActivePlatform" && !xmlReader.IsEmptyElement)
                                    ActivePlatform = xmlReader.ReadString().Trim();
                            }
                        }
                    }
                }
            }
            catch { }
        }

        #endregion

        #region /* RENDERING */

        /// <summary>
        /// Always <c>false</c>.
        /// </summary>
        public override bool IsRenderable
        {
            get { return false; }
        }

        public override void AsText(CodeWriter writer, RenderFlags flags)
        {
            if (flags.HasFlag(RenderFlags.Description))
                writer.Write(_name);
            else
            {
                // Write the file header
                writer.WriteLine();
                writer.WriteLine("Microsoft Visual Studio Solution File, Format Version " + _formatVersion);
                if (_productRelease != null)
                    writer.WriteLine(ProductReleasePrefix + _productRelease);

                // Write all project entries
                foreach (ProjectEntry projectEntry in _projectEntries)
                    projectEntry.AsText(writer);

                writer.WriteLine(GlobalStart);

                // Determine all unique configuration platforms across all projects so that we can generate the related global sections
                HashSet<string> uniqueConfigurations = new HashSet<string>();
                HashSet<string> uniquePlatforms = new HashSet<string>();
                bool hasNullPlatform = false;
                foreach (Project project in _projects)
                {
                    foreach (Project.Configuration projectConfiguration in project.Configurations)
                    {
                        uniqueConfigurations.Add(projectConfiguration.Name);
                        string platform = projectConfiguration.Platform;
                        if (platform == null)
                        {
                            uniquePlatforms.Add(PlatformDotNet);
                            hasNullPlatform = true;
                        }
                        else
                        {
                            if (platform == Project.PlatformAnyCPU)
                                platform = PlatformAnyCPU;
                            uniquePlatforms.Add(platform);
                        }
                    }
                }
                // If we have multiple platforms, and more than one project, add 'Mixed Platforms'
                if (uniquePlatforms.Count > (hasNullPlatform ? 2 : 1) && Projects.Count > 1)
                    uniquePlatforms.Add(PlatformMixed);
                List<string> uniqueConfigurationPlatforms = Enumerable.ToList((Enumerable.SelectMany<string, string, string>(uniqueConfigurations,
                    delegate(string configuration) { return uniquePlatforms; }, delegate(string configuration, string platform) { return configuration + "|" + platform; })));
                uniqueConfigurationPlatforms.Sort();

                // Write out all global sections
                foreach (GlobalSection globalSection in _globalSections)
                {
                    // Re-write the Solution's configuration platforms section based upon the project configurations,
                    // because it should always be safe to do this.
                    if (globalSection.Name == SolutionConfigurationPlatformsGlobalSection)
                    {
                        GlobalSection.AsTextHeader(writer, SolutionConfigurationPlatformsGlobalSection, true);
                        foreach (string configurationPlatform in uniqueConfigurationPlatforms)
                            writer.WriteLine("\t\t" + configurationPlatform + " = " + configurationPlatform);
                        GlobalSection.AsTextFooter(writer);
                    }
                    else
                        globalSection.AsText(writer);
                }

                writer.WriteLine(GlobalEnd);
            }
        }

        protected void AsTextProjectStart(CodeWriter writer, Guid typeGuid, string name, string fileName, Guid projectGuid)
        {
            writer.WriteLine(ProjectStart + "\"" + typeGuid.ToString("B").ToUpper() + "\") = \"" + name + "\", \""
                + FileUtil.MakeRelative(FileName, fileName) + "\", \"" + projectGuid.ToString("B").ToUpper() + "\"");
        }

        protected void AsTextProjectEnd(CodeWriter writer)
        {
            writer.WriteLine(ProjectEnd);
        }

        #endregion

        #region /* PROJECT ENTRY */

        /// <summary>
        /// Represents a project entry in a <see cref="Solution"/> file.
        /// </summary>
        public class ProjectEntry : CodeObject
        {
            #region /* FIELDS */

            public Guid TypeGuid;
            public string Name;
            public string FileName;
            public Guid Guid;
            public List<ProjectSection> ProjectSections = new List<ProjectSection>();

            /// <summary>
            /// The associated <see cref="Project"/> object.
            /// </summary>
            public Project Project;

            #endregion

            #region /* CONSTRUCTORS */

            /// <summary>
            /// Create a <see cref="ProjectEntry"/>.
            /// </summary>
            public ProjectEntry(Guid typeGuid, Project project)
            {
                TypeGuid = typeGuid;
                Name = project.Name;
                FileName = project.FileName;
                Guid = project.ProjectGuid;
                Project = project;
                Parent = project.Solution;
            }

            #endregion

            #region /* PROPERTIES */

            /// <summary>
            /// Determine if the ProjectEntry represents a folder instead of an actual project.
            /// </summary>
            public bool IsFolder
            {
                get { return (TypeGuid == Project.FolderType); }
            }

            /// <summary>
            /// The parent <see cref="Project"/>.
            /// </summary>
            public Solution ParentSolution
            {
                get { return _parent as Solution; }
            }

            #endregion

            #region /* METHODS */

            /// <summary>
            /// Find any ProjectSection with the specified name.
            /// </summary>
            public ProjectSection FindProjectSection(string name)
            {
                return Enumerable.FirstOrDefault(ProjectSections, delegate(ProjectSection projectSection) { return projectSection.Name == name; });
            }

            #endregion

            #region /* PARSING */

            /// <summary>
            /// Parse from the specified <see cref="StreamReader"/>.
            /// </summary>
            public ProjectEntry(StreamReader reader, Guid typeGuid, string name, string fileName, Guid guid, Solution parent)
            {
                Parent = parent;
                TypeGuid = typeGuid;
                Name = name;
                FileName = fileName;
                Guid = guid;

                do
                {
                    string line = ParentSolution.ReadLine(reader);
                    if (line == null || line == ProjectEnd) break;
                    if (line.StartsWith(ProjectSectionStart))
                        ProjectSections.Add(new ProjectSection(reader, line, parent));
                    else
                        parent.UnrecognizedLine(line);
                }
                while (true);
            }

            #endregion

            #region /* RENDERING */

            /// <summary>
            /// Write to the specified <see cref="CodeWriter"/>.
            /// </summary>
            public void AsText(CodeWriter writer)
            {
                ParentSolution.AsTextProjectStart(writer, TypeGuid, Name, FileName, Guid);
                foreach (ProjectSection projectSection in ProjectSections)
                    projectSection.AsText(writer);
                ParentSolution.AsTextProjectEnd(writer);
            }

            #endregion
        }

        #endregion

        #region /* PROJECT SECTION */

        /// <summary>
        /// Represents a project-level section of configuration data in a solution file.
        /// </summary>
        public class ProjectSection
        {
            #region /* FIELDS */

            public string Name;
            public bool IsPreProject;
            public List<KeyValuePair<string, string>> KeyValues = new List<KeyValuePair<string, string>>();

            #endregion

            #region /* CONSTRUCTORS */

            /// <summary>
            /// Create a <see cref="ProjectSection"/>.
            /// </summary>
            public ProjectSection(string name, bool isPreProject)
            {
                Name = name;
                IsPreProject = isPreProject;
            }

            #endregion

            #region /* METHODS */

            /// <summary>
            /// Find the value with the specified key.
            /// </summary>
            public string FindValue(string key)
            {
                return Enumerable.FirstOrDefault(KeyValues, delegate(KeyValuePair<string, string> keyValue) { return keyValue.Key == key; }).Value;
            }

            #endregion

            #region /* PARSING */

            /// <summary>
            /// Parse a <see cref="ProjectSection"/>.
            /// </summary>
            public ProjectSection(StreamReader reader, string line, Solution parent)
            {
                if (line.StartsWith(ProjectSectionStart))
                {
                    int start = ProjectSectionStart.Length;
                    int end = line.IndexOf(')', start);
                    if (end > 0)
                    {
                        Name = line.Substring(start, end - start);
                        IsPreProject = (line.Substring(end).Contains(PreProject));
                    }
                    else
                        parent.UnrecognizedLine(line);
                    do
                    {
                        line = parent.ReadLine(reader);
                        if (line == null || line == ProjectSectionEnd) break;
                        end = line.IndexOf(" = ");
                        if (end > 0)
                            KeyValues.Add(new KeyValuePair<string, string>(line.Substring(0, end), line.Substring(end + 3)));
                        else
                            parent.UnrecognizedLine(line);
                    }
                    while (true);
                }
            }

            #endregion

            #region /* RENDERING */

            public void AsText(CodeWriter writer)
            {
                AsTextHeader(writer, Name, IsPreProject);
                foreach (KeyValuePair<string, string> keyValuePair in KeyValues)
                    writer.WriteLine("\t\t" + keyValuePair.Key + " = " + keyValuePair.Value);
                AsTextFooter(writer);
            }

            public static void AsTextHeader(CodeWriter writer, string name, bool isPreProject)
            {
                writer.WriteLine("\t" + ProjectSectionStart + name + ") = " + (isPreProject ? PreProject : PostProject));
            }

            public static void AsTextFooter(CodeWriter writer)
            {
                writer.WriteLine("\t" + ProjectSectionEnd);
            }

            #endregion
        }

        #endregion

        #region /* GLOBAL SECTION */

        /// <summary>
        /// Represents a global-level section of configuration data in a solution file.
        /// </summary>
        public class GlobalSection
        {
            #region /* FIELDS */

            /// <summary>
            /// The name of the <see cref="GlobalSection"/>.
            /// </summary>
            public string Name;

            /// <summary>
            /// True if the <see cref="GlobalSection"/> is 'pre-solution' (otherwise, it is 'post-solution').
            /// </summary>
            public bool IsPreSolution;

            // Store the key/values in a Dictionary (for quick lookups and no duplicates), but also keep a List of KeyValuePairs
            // to preserve the order.  Also, the stupid VSS data has duplicate keys (appears to be a mistake, such as 'CanCheckoutShared'
            // missing a number on the end), so we ignore collisions when adding to the dictionary and such duplicate keys are ony
            // accessible via the List.
            private readonly Dictionary<string, string> _dictionary = new Dictionary<string, string>();
            private readonly List<KeyValuePair<string, string>> _keyValuePairs = new List<KeyValuePair<string, string>>();

            #endregion

            #region /* CONSTRUCTORS */

            /// <summary>
            /// Create a <see cref="GlobalSection"/>.
            /// </summary>
            public GlobalSection(string name, bool isPreSolution, params KeyValuePair<string, string>[] keyValuePairs)
            {
                Name = name;
                IsPreSolution = isPreSolution;
                foreach (KeyValuePair<string, string> keyValuePair in keyValuePairs)
                    AddKeyValue(keyValuePair.Key, keyValuePair.Value);
            }

            #endregion

            #region /* METHODS */

            /// <summary>
            /// The list of key/value pairs ordered as they appear in the solution file.
            /// </summary>
            public List<KeyValuePair<string, string>> KeyValuePairs
            {
                get { return _keyValuePairs; }
            }

            /// <summary>
            /// Add a key-value pair.
            /// </summary>
            public void AddKeyValue(string key, string value, bool allowDuplicateKeys)
            {
                bool keyExists = _dictionary.ContainsKey(key);
                if (!keyExists || allowDuplicateKeys)
                {
                    if (!keyExists)
                        _dictionary.Add(key, value);
                    _keyValuePairs.Add(new KeyValuePair<string, string>(key, value));
                }
            }

            /// <summary>
            /// Add a key-value pair.
            /// </summary>
            public void AddKeyValue(string key, string value)
            {
                AddKeyValue(key, value, false);
            }

            /// <summary>
            /// Find the value with the specified key (only looks in the dictionary, so won't find duplicate keys).
            /// </summary>
            public string FindValue(string key)
            {
                string value;
                _dictionary.TryGetValue(key, out value);
                return value;
            }

            /// <summary>
            /// Remove the key-value pair with the specified key.
            /// </summary>
            public void RemoveKey(string key)
            {
                string value;
                if (_dictionary.TryGetValue(key, out value))
                {
                    _dictionary.Remove(key);
                    _keyValuePairs.RemoveAll(delegate(KeyValuePair<string, string> keyValuePair) { return keyValuePair.Key == key && keyValuePair.Value == value; });
                }
            }

            #endregion

            #region /* PARSING */

            /// <summary>
            /// Parse a <see cref="GlobalSection"/> from the specified <see cref="StreamReader"/>.
            /// </summary>
            public GlobalSection(StreamReader reader, string line, Solution parent)
            {
                if (line.StartsWith(GlobalSectionStart))
                {
                    bool allowDuplicateKeys = true;
                    int start = GlobalSectionStart.Length;
                    int end = line.IndexOf(')', start);
                    if (end > 0)
                    {
                        Name = line.Substring(start, end - start);
                        if (Name == SolutionConfigurationPlatformsGlobalSection || Name == ProjectConfigurationPlatformsGlobalSection)
                            allowDuplicateKeys = false;
                        IsPreSolution = (line.Substring(end).Contains(PreSolution));
                    }
                    else
                        parent.UnrecognizedLine(line);
                    do
                    {
                        line = parent.ReadLine(reader);
                        if (line == null || line == GlobalSectionEnd) break;
                        end = line.IndexOf(" = ");
                        if (end > 0)
                            AddKeyValue(line.Substring(0, end), line.Substring(end + 3), allowDuplicateKeys);
                        else
                            parent.UnrecognizedLine(line);
                    }
                    while (true);
                }
            }

            #endregion

            #region /* RENDERING */

            public void AsText(CodeWriter writer)
            {
                AsTextHeader(writer, Name, IsPreSolution);
                foreach (KeyValuePair<string, string> keyValuePair in _keyValuePairs)
                    writer.WriteLine("\t\t" + keyValuePair.Key + " = " + keyValuePair.Value);
                AsTextFooter(writer);
            }

            public static void AsTextHeader(CodeWriter writer, string name, bool isPreSolution)
            {
                writer.WriteLine("\t" + GlobalSectionStart + name + ") = " + (isPreSolution ? PreSolution : PostSolution));
            }

            public static void AsTextFooter(CodeWriter writer)
            {
                writer.WriteLine("\t" + GlobalSectionEnd);
            }

            #endregion
        }

        #endregion
    }

    #region /* CODE ANNOTATION */

    /// <summary>
    /// Represents a 'listed' code <see cref="Annotation"/> and its location (<see cref="Project"/> and <see cref="CodeUnit"/>).
    /// Listed annotations include all <see cref="Message"/>s (error, warning, suggestion, etc) and also special <see cref="Comment"/>s.
    /// </summary>
    /// <remarks>
    /// <see cref="CodeAnnotation"/> objects are maintained at the <see cref="Solution"/> level in order to provide a single collection of
    /// all messages for the entire solution (for display in an output window).
    /// </remarks>
    public class CodeAnnotation
    {
        #region /* FIELDS */

        private readonly Annotation _annotation;
        private readonly Project _project;
        private readonly CodeUnit _codeUnit;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a <see cref="CodeAnnotation"/>.
        /// </summary>
        public CodeAnnotation(Annotation annotation, Project project, CodeUnit codeUnit)
        {
            _annotation = annotation;
            _project = project;
            _codeUnit = codeUnit;
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The descriptive category of the code object.
        /// </summary>
        public string Category
        {
            get { return (_annotation is Message ? ((Message)_annotation).Category : "ToDo"); }
        }

        /// <summary>
        /// The associated <see cref="Annotation"/>.
        /// </summary>
        public Annotation Annotation
        {
            get { return _annotation; }
        }

        /// <summary>
        /// The associated <see cref="Project"/>.
        /// </summary>
        public Project Project
        {
            get { return _project; }
        }

        /// <summary>
        /// The associated <see cref="CodeUnit"/>.
        /// </summary>
        public CodeUnit CodeUnit
        {
            get { return _codeUnit; }
        }

        /// <summary>
        /// The associated line number and column in "9999/99" format (if any, otherwise empty string).
        /// </summary>
        public string LineCol
        {
            get
            {
                int lineNumber = _annotation.LineNumber;
                return (lineNumber > 0) ? (lineNumber + "-" + _annotation.ColumnNumber) : "";
            }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Format the <see cref="CodeAnnotation"/> as a string.
        /// </summary>
        /// <returns>The text representation of the <see cref="CodeAnnotation"/>.</returns>
        public override string ToString()
        {
            string result = "";
            bool prefix = false;
            if (_project != null)
            {
                result += _project.Name;
                prefix = true;
            }
            if (_codeUnit != null)
            {
                if (prefix)
                    result += ": ";
                result += _codeUnit.Name;
                prefix = true;
            }
            if (prefix)
            {
                int lineNumber = _annotation.LineNumber;
                if (lineNumber > 0)
                    result += "(" + lineNumber + "," + _annotation.ColumnNumber + ")";
                result += ": ";
            }
            result += _annotation.AsString();
            return result;
        }

        #endregion
    }

    #endregion

    #region /* LOAD OPTIONS */

    /// <summary>
    /// Load options - used when loading a Solution, Project, or CodeUnit.
    /// </summary>
    [Flags]
    public enum LoadOptions
    {
        /// <summary>
        /// No options specified - loading will still occur, but no parsing, resolving, or extra logging.
        /// </summary>
        None = 0x00,

        /// <summary>
        /// Parse all <see cref="CodeUnit"/>s.
        /// </summary>
        ParseSources = 0x01,

        /// <summary>
        /// Resolve all <see cref="CodeUnit"/>s (also forces parsing).
        /// </summary>
        ResolveSources = 0x02,

        /// <summary>
        /// Log messages after loading/parsing/resolving (errors, warnings, others - depending upon the log level).
        /// </summary>
        /// <remarks>
        /// This option only determines whether or not messages are logged using the <see cref="Log"/> class (to the <see cref="Console"/>,
        /// or intercepted by <see cref="Log.SetLogWriteLineCallback"/>.  Regardless, <see cref="Message"/>s are always created and propagated
        /// up to the <see cref="Solution.CodeAnnotations"/> collection.
        /// </remarks>
        LogMessages = 0x08,

        /// <summary>
        /// Do not parse method bodies (useful if you only need types and member signatures, and not code in methods).
        /// </summary>
        DoNotParseBodies = 0x10,

        /// <summary>
        /// Do not resolve method bodies (useful if you have parsed method bodies, but do not need to resolve symbols in them).
        /// </summary>
        DoNotResolveBodies = 0x20,

        /// <summary>
        /// Perform complete processing.
        /// </summary>
        Complete = ParseSources | ResolveSources,

        /// <summary>
        /// Load, but don't parse or resolve sources (useful for working on <see cref="Solution"/> or <see cref="Project"/> files only).
        /// </summary>
        LoadOnly = None,

        /// <summary>
        /// Load and parse sources, but don't resolve (useful for formatting, when resolving is not required).
        /// </summary>
        DoNotResolve = ParseSources
    }

    #endregion

    #region /* LOAD STATUS */

    /// <summary>
    /// Used for status callbacks during the load process to monitor progress and update any UI.
    /// </summary>
    public enum LoadStatus
    {
        /// <summary>Starting to load a <see cref="Solution"/> or <see cref="Project"/>.</summary>
        Loading,

        /// <summary>Created a new <see cref="Solution"/>, <see cref="Project"/>, <see cref="Reference"/>, or <see cref="CodeUnit"/> object.</summary>
        ObjectCreated,

        /// <summary>A listed <see cref="Annotation"/> (such as an error or warning <see cref="Message"/>) was added to an object.</summary>
        ObjectAnnotated,

        /// <summary>A <see cref="Solution"/> file was loaded, and the active configuration and platform have been set - projects will be loaded next.</summary>
        SolutionLoaded,

        /// <summary>A <see cref="Project"/> file has been parsed, and the configuration and platform have been set.</summary>
        ProjectParsed,

        /// <summary>All <see cref="Project"/>s have been loaded.</summary>
        ProjectsLoaded,

        /// <summary>Starting parsing of all <see cref="CodeUnit"/>s.</summary>
        Parsing,

        /// <summary>Starting resolving of all <see cref="CodeUnit"/>s.</summary>
        Resolving,

        /// <summary>Starting logging of message counts, and messages (if requested).</summary>
        LoggingResults
    }

    #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)

Share

About the Author

KenBeckett
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).

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150302.1 | Last Updated 2 Dec 2012
Article Copyright 2012 by KenBeckett
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid