File: Microsoft\Build\Tasks\Xaml\PartialClassGenerationTaskInternal.cs
Project: ndp\cdf\src\NetFx40\XamlBuildTask\XamlBuildTask.csproj (XamlBuildTask)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
namespace Microsoft.Build.Tasks.Xaml
{
 
    using System;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Runtime;
    using System.Xaml;
    using System.Xaml.Schema;
    using System.Xml;
    using System.Reflection;
    using System.Globalization;
    using System.Runtime.Remoting.Lifetime;
    using Microsoft.Build.Utilities;
    using XamlBuildTask;
    using Microsoft.Build.Framework;
 
    internal class PartialClassGenerationTaskInternal : MarshalByRefObject
    {
        const string UnknownExceptionErrorCode = "XC1000";
 
        IList<ITaskItem> applicationMarkup;
        IList<string> generatedResources;
        IList<string> generatedCodeFiles;
        IList<ITaskItem> references;
        IList<string> sourceCodeFiles;
        HashSet<string> sourceFilePaths;
        IList<LogData> logData;
        IList<Assembly> assemblyNames;
        XamlSchemaContext schemaContext;
        HashSet<string> markupFileNames;
        IEnumerable<IXamlBuildTypeGenerationExtension> xamlBuildTypeGenerationExtensions;
        XamlBuildTypeGenerationExtensionContext buildContextForExtensions;
 
        // Set the lease lifetime according to the environment variable with the name defined by RemotingLeaseLifetimeInMinutesEnvironmentVariableName
        public override object InitializeLifetimeService()
        {
            ILease lease = (ILease)base.InitializeLifetimeService();
            XamlBuildTaskLeaseLifetimeHelper.SetLeaseLifetimeFromEnvironmentVariable(lease);
            return lease;
        }
 
        public IList<ITaskItem> ApplicationMarkup
        {
            get
            {
                if (this.applicationMarkup == null)
                {
                    this.applicationMarkup = new List<ITaskItem>();
                }
                return this.applicationMarkup;
            }
            set
            {
                this.applicationMarkup = value;
            }
        }
 
        public string AssemblyName
        { get; set; }
 
        public TaskLoggingHelper BuildLogger
        { get; set; }
 
        public XamlBuildTypeGenerationExtensionContext BuildContextForExtensions
        {
            get
            {
                if (this.buildContextForExtensions == null)
                {
                    XamlBuildTypeGenerationExtensionContext local = new XamlBuildTypeGenerationExtensionContext();
                    local.AssemblyName = this.AssemblyName;
                    local.IsInProcessXamlMarkupCompile = this.IsInProcessXamlMarkupCompile;
                    local.Language = this.Language;
                    local.OutputPath = this.OutputPath;
                    local.RootNamespace = this.RootNamespace;
                    local.AddSourceCodeFiles(this.SourceCodeFiles);
                    local.AddReferences(XamlBuildTaskServices.GetReferences(this.references));
                    local.XamlBuildLogger = this.BuildLogger;
 
                    this.buildContextForExtensions = local;
                }
                return this.buildContextForExtensions;
            }
        }
 
        public IList<string> GeneratedResources
        {
            get
            {
                if (this.generatedResources == null)
                {
                    this.generatedResources = new List<string>();
                }
                return this.generatedResources;
            }
        }
 
        public IList<string> GeneratedCodeFiles
        {
            get
            {
                if (this.generatedCodeFiles == null)
                {
                    this.generatedCodeFiles = new List<string>();
                }
                return generatedCodeFiles;
            }
        }
        
        public string GeneratedSourceExtension
        { get; set; }
 
        public string Language
        { get; set; }
 
        public IList<LogData> LogData
        {
            get
            {
                if (this.logData == null)
                {
                    this.logData = new List<LogData>();
                }
                return this.logData;
            }
        }
 
        public string OutputPath
        { get; set; }
 
        public IList<ITaskItem> References
        {
            get
            {
                if (this.references == null)
                {
                    this.references = new List<ITaskItem>();
                }
                return this.references;
            }
            set
            {
                this.references = value;
            }
        }
 
        public IList<Assembly> LoadedAssemblyList
        {
            get
            {
                if (this.assemblyNames == null)
                {
                    if (IsInProcessXamlMarkupCompile)
                    {
                        this.assemblyNames = cachedAssemblyList;
                    }
                    else
                    {
                        this.assemblyNames = new List<Assembly>();
                    }
                }
                return this.assemblyNames;
            }
            set
            {
                this.assemblyNames = value;
                if (IsInProcessXamlMarkupCompile)
                {
                    cachedAssemblyList = value;
                }
            }
        }
 
        public string MSBuildProjectDirectory
        { get; set; }
 
        private static IList<Assembly> cachedAssemblyList = null;
 
        public bool IsInProcessXamlMarkupCompile
        { get; set; }
 
        public string RootNamespace
        { get; set; }
 
        public IList<string> SourceCodeFiles
        {
            get
            {
                if (this.sourceCodeFiles == null)
                {
                    this.sourceCodeFiles = new List<string>();
                }
                return this.sourceCodeFiles;
            }
            set
            {
                this.sourceCodeFiles = value;
            }
        }
 
        public bool RequiresCompilationPass2
        { get; set; }
 
        public bool SupportExtensions
        { get; set; }
 
        HashSet<string> SourceFilePaths
        {
            get
            {
                if (sourceFilePaths == null)
                {
                    sourceFilePaths = new HashSet<string>();
                    if (SourceCodeFiles != null)
                    {
                        foreach (string sourceCodeFile in SourceCodeFiles)
                        {
                            sourceFilePaths.Add(sourceCodeFile);
                        }
                    }
                }
                return sourceFilePaths;
            }
            set
            {
                this.sourceFilePaths = value;
            }
        }
 
        public XamlSchemaContext SchemaContext
        {
            get
            {
                if (schemaContext == null)
                {
                    if (LoadedAssemblyList.Count > 0)
                    {
                        schemaContext = new XamlSchemaContext(LoadedAssemblyList);
                    }
                    else
                    {
                        schemaContext = new XamlSchemaContext();
                    }
                }
                return schemaContext;
            }
        }
 
        public string HelperClassFullName
        { get; set; }
 
        public IList<Tuple<string, string, string>> XamlBuildTaskTypeGenerationExtensionNames
        {
            get;
            set;
        }
 
        public bool MarkupCompilePass2ExtensionsPresent
        {
            get;
            set;
        } 
 
        public bool Execute()
        {
            try
            {
                if (this.ApplicationMarkup == null || this.ApplicationMarkup.Count == 0)
                {
                    return true;
                }
                if (!CodeDomProvider.IsDefinedLanguage(this.Language))
                {
                    throw FxTrace.Exception.Argument("Language", SR.UnrecognizedLanguage(this.Language));
                }
 
                if (this.SupportExtensions)
                {
                    this.xamlBuildTypeGenerationExtensions = XamlBuildTaskServices.GetXamlBuildTaskExtensions<IXamlBuildTypeGenerationExtension>(
                        this.XamlBuildTaskTypeGenerationExtensionNames,
                        this.BuildLogger,
                        this.MSBuildProjectDirectory);
                }
 
                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(XamlBuildTaskServices.ReflectionOnlyAssemblyResolve);
                bool retVal = true;
                // We load the assemblies for the real builds
                // For intellisense builds, we load them the first time only
                if (!IsInProcessXamlMarkupCompile || this.LoadedAssemblyList == null)
                {
                    if (this.References != null)
                    {
                        try
                        {
                            this.LoadedAssemblyList = XamlBuildTaskServices.Load(this.References, IsInProcessXamlMarkupCompile);
                        }
                        catch (FileNotFoundException e)
                        {
                            XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, e.FileName, 0, 0);
                            retVal = false;
                        }
                    }
                }
 
                CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider(this.Language);
 
                ProcessHelperClassGeneration(codeDomProvider); 
                foreach (ITaskItem app in ApplicationMarkup)
                {
                    string inputMarkupFile = app.ItemSpec;
                    try
                    {
                        retVal &= ProcessMarkupItem(app, codeDomProvider);
                    }
                    catch (LoggableException e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
                        XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, e.Source, e.LineNumber, e.LinePosition);
                        retVal = false;
                    }
                    catch (FileLoadException e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
 
                        XamlBuildTaskServices.LogException(this.BuildLogger, SR.AssemblyCannotBeResolved(XamlBuildTaskServices.FileNotLoaded), inputMarkupFile, 0, 0);
                        retVal = false;
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
                        XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, inputMarkupFile, 0, 0);
                        retVal = false;
                    }
                }
 
                // Add the files generated from extensions
                if (this.SupportExtensions)
                {
                    if (retVal)
                    {
                        foreach (string fileName in this.BuildContextForExtensions.GeneratedFiles)
                        {
                            this.GeneratedCodeFiles.Add(fileName);
                        }
 
                        foreach (string fileName in this.BuildContextForExtensions.GeneratedResourceFiles)
                        {
                            this.GeneratedResources.Add(fileName);
                        }
                    }
                }
 
                return retVal;
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
 
                // Log unknown errors that do not originate from the task.
                // Assumes that all known errors are logged when the exception is thrown.
                if (!(e is LoggableException))
                {
                    XamlBuildTaskServices.LogException(this.BuildLogger, e.Message);
                }
                return false;
            }
        }
 
        void ProcessHelperClassGeneration(CodeDomProvider codeDomProvider)
        {
            string codeFileName = "_" + this.AssemblyName + GetGeneratedSourceExtension(codeDomProvider);
            codeFileName = Path.Combine(this.OutputPath, codeFileName);
 
 
            string namespaceName = "XamlStaticHelperNamespace";
            string className = "_XamlStaticHelper";
 
            // Generate code file
            CodeCompileUnit codeUnit = new ClassGenerator(this.BuildLogger, codeDomProvider, this.Language).GenerateHelperClass(namespaceName, className, this.LoadedAssemblyList);
            WriteCode(codeDomProvider, codeUnit, codeFileName);
            this.GeneratedCodeFiles.Add(codeFileName);
 
            this.HelperClassFullName = namespaceName + "." + className;
        }
 
        bool ProcessMarkupItem(ITaskItem markupItem, CodeDomProvider codeDomProvider)
        {
            string markupItemFileName = markupItem.ItemSpec;
            XamlBuildTaskServices.PopulateModifiers(codeDomProvider);
 
            XamlNodeList xamlNodes = ReadXamlNodes(markupItemFileName);
            if (xamlNodes == null)
            {
                return false;
            }
 
            ClassData classData = ReadClassData(xamlNodes, markupItemFileName);
 
            string outputFileName = GetFileName(markupItemFileName);
            string codeFileName = Path.ChangeExtension(outputFileName, GetGeneratedSourceExtension(codeDomProvider));
            string markupFileName = Path.ChangeExtension(outputFileName, GeneratedSourceExtension + XamlBuildTaskServices.XamlExtension);
            classData.EmbeddedResourceFileName = Path.GetFileName(markupFileName);
            classData.HelperClassFullName = this.HelperClassFullName;
 
            // Check if code file with partial class exists
            classData.SourceFileExists = UserProvidedFileExists(markupItemFileName, codeDomProvider);
 
            // Store the full type name as metadata on the markup item
            string rootNamespacePrefix = null;
            string namespacePrefix = null;
            string typeFullName = null;
            if (this.Language.Equals("VB") && !String.IsNullOrWhiteSpace(classData.RootNamespace))
            {
                rootNamespacePrefix = classData.RootNamespace + ".";
            }
 
            if (!String.IsNullOrWhiteSpace(classData.Namespace))
            {
                namespacePrefix = classData.Namespace + ".";
            }
 
            if (rootNamespacePrefix != null)
            {
                if (namespacePrefix != null)
                {
                    typeFullName = rootNamespacePrefix + namespacePrefix + classData.Name;
                }
                else
                {
                    typeFullName = rootNamespacePrefix + classData.Name;
                }
            }
            else
            {
                if (namespacePrefix != null)
                {
                    typeFullName = namespacePrefix + classData.Name;
                }
                else
                {
                    typeFullName = classData.Name;
                }
            }
 
            markupItem.SetMetadata("typeName", typeFullName);
 
            // Execute extensions here to give them a chance to mutate the ClassData before we generate code.
            if (this.SupportExtensions)
            {
                if (!ExecuteExtensions(classData, markupItem))
                {
                    return false;
                }
            }
 
            // Generate code file
            CodeCompileUnit codeUnit = new ClassGenerator(this.BuildLogger, codeDomProvider, this.Language).Generate(classData);         
            WriteCode(codeDomProvider, codeUnit, codeFileName);
            this.GeneratedCodeFiles.Add(codeFileName);
 
            // Generate resource file
            if (!string.IsNullOrEmpty(this.AssemblyName))
            {
                // Generate xaml "implementation" file
                XmlWriterSettings xmlSettings = new XmlWriterSettings { Indent = true, IndentChars = "  ", CloseOutput = true };
                using (XmlWriter xmlWriter = XmlWriter.Create(File.Open(markupFileName, FileMode.Create), xmlSettings))
                {
                    XamlXmlWriterSettings xamlSettings = new XamlXmlWriterSettings() { CloseOutput = true };
                    
                    // Process EmbeddedResourceXaml to remove xml:space="preserve"
                    // due to a bug in XamlXmlWriter. XamlXmlWriter throws
                    // if there are duplicate xml:space attributes.
                    // It is ok to remove the xml:space attribute
                    // as the XamlXmlWriter would add it in the next step
                    // if needed.
                    RemoveXamlSpaceAttribute(classData);
 
                    using (XamlReader reader = classData.EmbeddedResourceXaml.GetReader())
                    {
                        using (XamlXmlWriter xamlWriter = new XamlXmlWriter(xmlWriter, reader.SchemaContext, xamlSettings))
                        {
                            XamlServices.Transform(reader, xamlWriter);                                                     
                        }
                    }
                }
                this.GeneratedResources.Add(markupFileName);
            }
 
            if (classData.RequiresCompilationPass2)
            {
                this.RequiresCompilationPass2 = true;
            }
            else
            {
                if (!this.SupportExtensions)
                {
                    if (!ValidateXaml(xamlNodes, markupItemFileName))
                    {
                        this.RequiresCompilationPass2 = true;
                    }
                }
                else
                {
                    // skip validation if we are doing in-proc compile
                    // OR if we have pass 2 extensions hooked up 
                    // as we anyway need to run pass 2 in that case
                    if (!this.IsInProcessXamlMarkupCompile && !this.MarkupCompilePass2ExtensionsPresent)
                    {
                        if (!ValidateXaml(xamlNodes, markupItemFileName))
                        {
                            this.RequiresCompilationPass2 = true;
                        }
                    }
                }
            }
            return true;
        }
 
        bool ExecuteExtensions(ClassData classData, ITaskItem markupItem)
        {
            // Execute pass1 extensions only 
            // we skip pass1 extensions if we are doing in-proc compile
            if (!this.IsInProcessXamlMarkupCompile)
            {
                bool extensionExecutedSuccessfully = true;
                foreach (IXamlBuildTypeGenerationExtension extension in this.xamlBuildTypeGenerationExtensions)
                {
                    if (extension == null)
                    {
                        continue;
                    }
 
                    this.BuildContextForExtensions.InputTaskItem = markupItem;
                    try
                    {
                        extensionExecutedSuccessfully = extension.Execute(classData, this.BuildContextForExtensions) && extensionExecutedSuccessfully;
                    }
                    catch (Exception e)
                    {
                        if (Fx.IsFatal(e))
                        {
                            throw;
                        }
                        throw FxTrace.Exception.AsError(new LoggableException(SR.ExceptionThrownInExtension(extension.ToString(), e.GetType().ToString(), e.Message)));
                    }
                }
 
                if (this.BuildLogger.HasLoggedErrors || !extensionExecutedSuccessfully)
                {
                    return false;
                }
            }
 
            return true;
        }
 
        string GetFileName(string markupItem)
        {
            if (markupFileNames == null)
            {
                markupFileNames = new HashSet<string>();
            }
 
            string originalMarkupItemName = Path.GetFileNameWithoutExtension(markupItem);
            string markupItemName = originalMarkupItemName;
            int i = 0;
            while (this.markupFileNames.Contains(markupItemName))
            {
                markupItemName = originalMarkupItemName + "." + (++i).ToString(CultureInfo.InvariantCulture);
            }
            this.markupFileNames.Add(markupItemName);
            markupItemName = markupItemName + Path.GetExtension(markupItem);
            if (this.OutputPath == null)
            {
                throw FxTrace.Exception.AsError(
                        new InvalidOperationException(SR.OutputPathCannotBeNull));
            }
            return Path.Combine(this.OutputPath, markupItemName);
        }
 
        [SuppressMessage("Microsoft.Security.Xml", "CA3053:UseXmlSecureResolver", 
            Justification = @"For the call to XmlReader.Create() below, CA3053 recommends setting the 
XmlReaderSettings.XmlResolver property to either null or an instance of XmlSecureResolver. 
But after setting this property to null, a warning of CA3053 still shows up in FxCop. 
So we suppress this error until the reporting for CA3053 has been updated to fix this issue.")]
        XamlNodeList ReadXamlNodes(string xamlFileName)
        {
            XamlNodeList nodeList = new XamlNodeList(this.SchemaContext);
 
            try
            {
                XamlXmlReaderSettings settings = new XamlXmlReaderSettings
                {
                    AllowProtectedMembersOnRoot = true,
                    ProvideLineInfo = true
                };
 
                using (StreamReader streamReader = new StreamReader(xamlFileName))
                {
                    XamlReader reader = new XamlXmlReader(XmlReader.Create(streamReader, new XmlReaderSettings { XmlResolver = null }), this.SchemaContext, settings);
                    XamlServices.Transform(reader, nodeList.Writer);
                }
            }
            catch (XmlException e)
            {
                XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, xamlFileName, e.LineNumber, e.LinePosition); 
                return null;
            }
            catch (XamlException e)
            {
                XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, xamlFileName, e.LineNumber, e.LinePosition);
                return null;
            }
            
            if (nodeList.Count > 0)
            {
                return nodeList;
            }
            else
            {
                return null;
            }
        }
 
        ClassData ReadClassData(XamlNodeList xamlNodes, string xamlFileName)
        {
            ClassImporter importer = new ClassImporter(xamlFileName, this.AssemblyName, this.Language.Equals("VB") ? this.RootNamespace : null);
            ClassData classData = importer.ReadFromXaml(xamlNodes);
            return classData;
        }
 
        void RemoveXamlSpaceAttribute(ClassData classData)
        {
            using (XamlReader reader = classData.EmbeddedResourceXaml.GetReader())
            {
                XamlNodeList newList = new XamlNodeList(reader.SchemaContext);
                using (XamlWriter writer = newList.Writer)
                {
                    bool nodesAvailable = reader.Read();
                    while (nodesAvailable)
                    {                        
                        if (reader.NodeType == XamlNodeType.StartMember && reader.Member == XamlLanguage.Space)
                        {
                            reader.Skip();
                        }
                        else
                        {
                            writer.WriteNode(reader);
                            nodesAvailable = reader.Read();
                        }
                    }
                }
                classData.EmbeddedResourceXaml = newList;
            }
        }
 
        bool ValidateXaml(XamlNodeList xamlNodeList, string xamlFileName)
        {
            using (XamlReader xamlReader = xamlNodeList.GetReader())
            {
                IList<LogData> validationErrors = null;
                ClassValidator validator = new ClassValidator(xamlFileName, null, null);
                return validator.ValidateXaml(xamlReader, true, this.AssemblyName, out validationErrors);
            }
        }
 
        void WriteCode(CodeDomProvider provider, CodeCompileUnit codeUnit, string fileName)
        {
            using (StreamWriter fileStream = new StreamWriter(fileName))
            {
                using (IndentedTextWriter tw = new IndentedTextWriter(fileStream))
                {
                    provider.GenerateCodeFromCompileUnit(codeUnit, tw, new CodeGeneratorOptions());
                }
            }
        }
 
        string GetGeneratedSourceExtension(CodeDomProvider codeDomProvider)
        {
            string result = null;
            if (!string.IsNullOrEmpty(this.GeneratedSourceExtension))
            {
                result = this.GeneratedSourceExtension;
                if (!result.StartsWith(".", StringComparison.Ordinal))
                {
                    result = "." + result;
                }
            }
            return result + "." + codeDomProvider.FileExtension;
        }
 
        bool UserProvidedFileExists(string markupItemPath, CodeDomProvider codeDomProvider)
        {
            string desiredSourceFilePath = Path.ChangeExtension(markupItemPath, "xaml." + codeDomProvider.FileExtension);
            return SourceFilePaths.Contains(desiredSourceFilePath);
        }
    }
}