File: compmod\system\codedom\compiler\CodeDomConfigurationHandler.cs
Project: ndp\fx\src\System.csproj (System)
//------------------------------------------------------------------------------
// <OWNER>Microsoft</OWNER>
// 
// <copyright file="CodeDomCompilationConfiguration.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
 * Code related to the <assemblies> config section
 *
 * Copyright (c) 1999 Microsoft Corporation
 */
 
namespace System.CodeDom.Compiler {
 
    using System;
    using System.CodeDom;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Collections.Generic;
    using System.Configuration;
    using System.IO;
    using System.Reflection;
    using System.Runtime.Serialization.Formatters;
    using System.Xml;
    using System.Globalization;
    
    internal class CodeDomCompilationConfiguration {
 
        internal const String sectionName = "system.codedom";
 
        static readonly char[] s_fieldSeparators = new char[] {';'};
 
        // _compilerLanguages : Hashtable <string, CompilerInfo>
        internal Hashtable _compilerLanguages;
 
        // _compilerExtensions : Hashtable <string, CompilerInfo>
        internal Hashtable _compilerExtensions;
        internal ArrayList _allCompilerInfo;
        
        private static CodeDomCompilationConfiguration defaultInstance = new CodeDomCompilationConfiguration();
        
        internal static CodeDomCompilationConfiguration Default {
            get {
                return defaultInstance;
            }
        }
 
        internal CodeDomCompilationConfiguration() {
            // First time initialization. This must be kept consistent with machine.config.comments in that it 
            // must initialize the config system as if that block was present.
 
            _compilerLanguages = new Hashtable(StringComparer.OrdinalIgnoreCase);
            _compilerExtensions = new Hashtable(StringComparer.OrdinalIgnoreCase);
            _allCompilerInfo = new ArrayList();            
            
            CompilerInfo compilerInfo;
            CompilerParameters compilerParameters;
            String typeName;
            
            // C#
            compilerParameters = new CompilerParameters();
            compilerParameters.WarningLevel = 4;
            typeName = "Microsoft.CSharp.CSharpCodeProvider, " + AssemblyRef.System;
            compilerInfo = new CompilerInfo(compilerParameters, typeName);
            compilerInfo._compilerLanguages = new string[] {"c#", "cs", "csharp"};
            compilerInfo._compilerExtensions = new string[] {".cs", "cs"};
            compilerInfo._providerOptions = new Dictionary<string, string>();
            compilerInfo._providerOptions[RedistVersionInfo.NameTag] = RedistVersionInfo.DefaultVersion;
            AddCompilerInfo(compilerInfo);
 
            // VB
            compilerParameters = new CompilerParameters();
            compilerParameters.WarningLevel = 4;
            typeName = "Microsoft.VisualBasic.VBCodeProvider, " + AssemblyRef.System;
            compilerInfo = new CompilerInfo(compilerParameters, typeName);
            compilerInfo._compilerLanguages = new string[] {"vb", "vbs", "visualbasic", "vbscript"};
            compilerInfo._compilerExtensions = new string[] {".vb", "vb"};
            compilerInfo._providerOptions = new Dictionary<string, string>();
            compilerInfo._providerOptions[RedistVersionInfo.NameTag] = RedistVersionInfo.DefaultVersion;
            AddCompilerInfo(compilerInfo);
 
            // JScript
            compilerParameters = new CompilerParameters();
            compilerParameters.WarningLevel = 4;
            typeName = "Microsoft.JScript.JScriptCodeProvider, " + AssemblyRef.MicrosoftJScript;
            compilerInfo = new CompilerInfo(compilerParameters, typeName);
            compilerInfo._compilerLanguages = new string[] {"js", "jscript", "javascript"};
            compilerInfo._compilerExtensions = new string[] {".js", "js"};
            compilerInfo._providerOptions = new Dictionary<string, string>();
            AddCompilerInfo(compilerInfo);
 
            // C++
            compilerParameters = new CompilerParameters();
            compilerParameters.WarningLevel = 4;
            typeName = "Microsoft.VisualC.CppCodeProvider, " + AssemblyRef.MicrosoftVisualCCppCodeProvider;
            compilerInfo = new CompilerInfo(compilerParameters, typeName);
            compilerInfo._compilerLanguages = new string[] {"c++", "mc", "cpp"};
            compilerInfo._compilerExtensions = new string[] {".h", "h"};
            compilerInfo._providerOptions = new Dictionary<string, string>();
            AddCompilerInfo(compilerInfo);
            
        }
 
        private CodeDomCompilationConfiguration(CodeDomCompilationConfiguration original) {
            if (original._compilerLanguages != null)
                _compilerLanguages  = (Hashtable)original._compilerLanguages.Clone();
 
            if (original._compilerExtensions != null)
                _compilerExtensions = (Hashtable)original._compilerExtensions.Clone();
 
            if (original._allCompilerInfo != null)
                _allCompilerInfo = (ArrayList)original._allCompilerInfo.Clone();
        }
        
        private void AddCompilerInfo(CompilerInfo compilerInfo) {
        
            foreach (string language in compilerInfo._compilerLanguages) {
                _compilerLanguages[language] = compilerInfo;
            }
 
            foreach (string extension in compilerInfo._compilerExtensions) {
                _compilerExtensions[extension] = compilerInfo;
            }
 
            _allCompilerInfo.Add(compilerInfo);
        }
 
        private void RemoveUnmapped() {
            // Allow config compilers to replace redundant compiler entries
 
            // clear out the mapped marker
            for (int i = 0; i < _allCompilerInfo.Count; i++) {
                ((CompilerInfo)_allCompilerInfo[i])._mapped = false;
            }
 
            // Re-mark only the ones that still have a mapping
            foreach (CompilerInfo destinationCompilerInfo in _compilerLanguages.Values) {
                destinationCompilerInfo._mapped = true;
            }
            foreach (CompilerInfo destinationCompilerInfo in _compilerExtensions.Values) {
                destinationCompilerInfo._mapped = true;
            }
 
            // Remove the ones that were not marked
            for (int i = _allCompilerInfo.Count - 1; i >= 0; i--) {
                if (!((CompilerInfo)_allCompilerInfo[i])._mapped) {
                    _allCompilerInfo.RemoveAt(i);
                }
            }
        }
 
        internal class SectionHandler {
            private SectionHandler () {
            }
 
            internal static object CreateStatic(object inheritedObject, XmlNode node) {
                CodeDomCompilationConfiguration inherited = (CodeDomCompilationConfiguration)inheritedObject;
                CodeDomCompilationConfiguration result;
 
                if (inherited == null)
                    result = new CodeDomCompilationConfiguration();
                else
                    result = new CodeDomCompilationConfiguration(inherited);
 
                HandlerBase.CheckForUnrecognizedAttributes(node);
 
                //
                // Handle child elements (if they exist)
                //   - compilers
                //
                foreach (XmlNode child in node.ChildNodes) {
 
                    // skip whitespace and comments
                    // reject nonelements
                    if (HandlerBase.IsIgnorableAlsoCheckForNonElement(child))
                        continue;
 
                    // handle <compilers>
                    if (child.Name == "compilers") {
                        ProcessCompilersElement(result, child);
                    }
                    else {
                        HandlerBase.ThrowUnrecognizedElement(child);
                    }
                }
 
                return result;
            }
 
            private static IDictionary<string, string> GetProviderOptions(XmlNode compilerNode) {
                Dictionary<string, string> res = new Dictionary<string, string>();
 
                foreach (XmlNode child in compilerNode) {
                    if (child.Name != "providerOption") {
                        HandlerBase.ThrowUnrecognizedElement(child);
                    }
 
                    string name = null, value = null;
                    HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "name", ref name);
                    HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "value", ref value);
                    HandlerBase.CheckForUnrecognizedAttributes(child);
                    HandlerBase.CheckForChildNodes(child);
 
                    res[name] = value;
                }
 
                return res;
            }
 
            private static void ProcessCompilersElement(CodeDomCompilationConfiguration result, XmlNode node) {
 
                // reject attributes
                HandlerBase.CheckForUnrecognizedAttributes(node);
 
                String configFile = ConfigurationErrorsException.GetFilename(node);
 
                foreach(XmlNode child in node.ChildNodes) {
                    int configLineNumber = ConfigurationErrorsException.GetLineNumber(child);
 
                    // skip whitespace and comments
                    // reject nonelements
                    if (HandlerBase.IsIgnorableAlsoCheckForNonElement(child))
                        continue;
 
                    if (child.Name != "compiler") {
                        HandlerBase.ThrowUnrecognizedElement(child);
                    }
 
                    String languages = String.Empty;
                    XmlNode languageNode = HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "language", ref languages);
                    String extensions = String.Empty;
                    HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "extension", ref extensions);
                    String compilerTypeName = null;
                    HandlerBase.GetAndRemoveStringAttribute(child, "type", ref compilerTypeName);
 
                    // Create a CompilerParameters for this compiler.
                    CompilerParameters compilerParams = new CompilerParameters();
 
                    int warningLevel = 0;
                    if (HandlerBase.GetAndRemoveNonNegativeIntegerAttribute(child, "warningLevel", ref warningLevel) != null) {
                        compilerParams.WarningLevel = warningLevel;
 
                        // Need to be false if the warning level is 0
                        compilerParams.TreatWarningsAsErrors = (warningLevel>0);
                    }
                    String compilerOptions = null;
                    if (HandlerBase.GetAndRemoveStringAttribute(child, "compilerOptions", ref compilerOptions) != null) {
                        compilerParams.CompilerOptions = compilerOptions;
                    }
 
                    IDictionary<string, string> provOptions = GetProviderOptions(child);
 
                    HandlerBase.CheckForUnrecognizedAttributes(child);
 
                    // Parse the semicolon separated lists
                    string[] languageList = languages.Split(s_fieldSeparators);
                    string[] extensionList = extensions.Split(s_fieldSeparators);
 
                    for( int i =0 ; i < languageList.Length; i++) {
                        languageList[i] = languageList[i].Trim();
                    }
 
                    for( int i =0 ; i < extensionList.Length; i++) {
                        extensionList[i] = extensionList[i].Trim();
                    }
 
 
                    // Validate language names, language names must have length and extensions must start with a period.
                    foreach (string language in languageList) {
                        if (language.Length == 0)
                            throw new ConfigurationErrorsException(SR.GetString(SR.Language_Names_Cannot_Be_Empty));
                    }
 
                    foreach (string extension in extensionList) {
                        if (extension.Length == 0 || extension[0] != '.')
                            throw new ConfigurationErrorsException(SR.GetString(SR.Extension_Names_Cannot_Be_Empty_Or_Non_Period_Based));
                    }
 
                    
                    // Create a CompilerInfo structure for this compiler, or get
                    // an existing one if no type was provided
                    CompilerInfo compilerInfo = null;
                    if (compilerTypeName != null) {
                        compilerInfo = new CompilerInfo(compilerParams, compilerTypeName);
                    } else {
                        // reconfiguring an existing entry
                        compilerInfo = result.FindExistingCompilerInfo(languageList, extensionList);
 
                        if (compilerInfo == null) 
                            throw new ConfigurationErrorsException();                        
                    }
 
                    compilerInfo.configFileName = configFile;
                    compilerInfo.configFileLineNumber = configLineNumber;
 
 
                    if (compilerTypeName != null) {
                        compilerInfo._compilerLanguages = languageList;
                        compilerInfo._compilerExtensions = extensionList;
                        compilerInfo._providerOptions = provOptions;
 
                        result.AddCompilerInfo(compilerInfo);
                    } else {
                        // merge in new options, replacing any previous values w/
                        // new ones.
                        foreach (KeyValuePair<string, string> kvp in provOptions) {
                            compilerInfo._providerOptions[kvp.Key] = kvp.Value;
                        }
                    }
                }
                // Allow config options to replace redundant compiler entries
                result.RemoveUnmapped();
            }
        }
 
        private CompilerInfo FindExistingCompilerInfo(string[] languageList, string[] extensionList) {
            CompilerInfo compilerInfo = null;
 
            foreach (CompilerInfo ci in _allCompilerInfo) {
                if (ci._compilerExtensions.Length == extensionList.Length &&
                    ci._compilerLanguages.Length == languageList.Length) {
 
                    bool differ = false;
                    for (int i = 0; i < ci._compilerExtensions.Length; i++) {
                        if (ci._compilerExtensions[i] != extensionList[i]) {
                            differ = true;
                            break;
                        }
                    }
 
                    for (int i = 0; i < ci._compilerLanguages.Length; i++) {
                        if (ci._compilerLanguages[i] != languageList[i]) {
                            differ = true;
                            break;
                        }
                    }
 
                    if (!differ) {
                        compilerInfo = ci;
                        break;
                    }
                }
            }
 
            return compilerInfo;
        }
    }
 
    internal class CodeDomConfigurationHandler : IConfigurationSectionHandler {
        internal CodeDomConfigurationHandler() {
        }
 
        public virtual object Create(object inheritedObject, object configContextObj, XmlNode node) {
            return CodeDomCompilationConfiguration.SectionHandler.CreateStatic(inheritedObject, node);
        }
    }
 
 
 
 
    internal static class HandlerBase {
 
        //
        // XML Attribute Helpers
        //
 
        private static XmlNode GetAndRemoveAttribute(XmlNode node, string attrib, bool fRequired) {
            XmlNode a = node.Attributes.RemoveNamedItem(attrib);
 
            // If the attribute is required and was not present, throw
            if (fRequired && a == null) {
                throw new ConfigurationErrorsException(
                    SR.GetString(SR.Config_missing_required_attribute, attrib, node.Name), node);
            }
 
            return a;
        }
 
        private static XmlNode GetAndRemoveStringAttributeInternal(XmlNode node, string attrib, bool fRequired, ref string val) {
            XmlNode a = GetAndRemoveAttribute(node, attrib, fRequired);
            if (a != null)
                val = a.Value;
 
            return a;
        }
 
        internal static XmlNode GetAndRemoveStringAttribute(XmlNode node, string attrib, ref string val) {
            return GetAndRemoveStringAttributeInternal(node, attrib, false /*fRequired*/, ref val);
        }
 
        internal static XmlNode GetAndRemoveRequiredNonEmptyStringAttribute(XmlNode node, string attrib, ref string val) {
            return GetAndRemoveNonEmptyStringAttributeInternal(node, attrib, true /*fRequired*/, ref val);
        }
 
        private static XmlNode GetAndRemoveNonEmptyStringAttributeInternal(XmlNode node, string attrib, bool fRequired, ref string val) {
            XmlNode a = GetAndRemoveStringAttributeInternal(node, attrib, fRequired, ref val);
            if (a != null && val.Length == 0) {
                throw new ConfigurationErrorsException(
                    SR.GetString(SR.Empty_attribute, attrib), a);
            }
 
            return a;
        }
 
        private static XmlNode GetAndRemoveIntegerAttributeInternal(XmlNode node, string attrib, bool fRequired, ref int val) {
            XmlNode a = GetAndRemoveAttribute(node, attrib, fRequired);
            if (a != null) {
                if (a.Value.Trim() != a.Value) {
                    throw new ConfigurationErrorsException(SR.GetString(SR.Config_invalid_integer_attribute, a.Name),a);
                }
 
                try {
                    val = int.Parse(a.Value, CultureInfo.InvariantCulture);
                }
                catch (Exception e) {
                    throw new ConfigurationErrorsException(
                        SR.GetString(SR.Config_invalid_integer_attribute, a.Name), e, a);
                }
            }
 
            return a;
        }
 
        private static XmlNode GetAndRemoveNonNegativeAttributeInternal(XmlNode node, string attrib, bool fRequired, ref int val) {
            XmlNode a = GetAndRemoveIntegerAttributeInternal(node, attrib, fRequired, ref val);
 
            if (a != null && val < 0) {
                throw new ConfigurationErrorsException(
                    SR.GetString(SR.Invalid_nonnegative_integer_attribute, attrib), a);
            }
 
            return a;
        }
 
        internal static XmlNode GetAndRemoveNonNegativeIntegerAttribute(XmlNode node, string attrib, ref int val) {
            return GetAndRemoveNonNegativeAttributeInternal(node, attrib, false /*fRequired*/, ref val);
        }
 
        internal static void CheckForUnrecognizedAttributes(XmlNode node) {
            if (node.Attributes.Count != 0) {
                throw new ConfigurationErrorsException(
                                SR.GetString(SR.Config_base_unrecognized_attribute, node.Attributes[0].Name),
                                node.Attributes[0]);
            }
        }
 
        //
        // XML Element Helpers
        //
 
        internal static void CheckForNonElement(XmlNode node) {
            if (node.NodeType != XmlNodeType.Element) {
                throw new ConfigurationErrorsException(
                                SR.GetString(SR.Config_base_elements_only),
                                node);                
            }
        }
 
 
        internal static bool IsIgnorableAlsoCheckForNonElement(XmlNode node) {
            if (node.NodeType == XmlNodeType.Comment || node.NodeType == XmlNodeType.Whitespace) {
                return true;
            }
 
            CheckForNonElement(node);
 
            return false;
        }
 
        internal static void CheckForChildNodes(XmlNode node) {
            if (node.HasChildNodes) {
                throw new ConfigurationErrorsException(
                                SR.GetString(SR.Config_base_no_child_nodes),
                                node.FirstChild);                
            }
        }
 
        internal static void ThrowUnrecognizedElement(XmlNode node) {
            throw new ConfigurationErrorsException(
                            SR.GetString(SR.Config_base_unrecognized_element),
                            node);
        }
 
    }
}