// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixBuildTools.MsgGen { using System; using System.CodeDom; using System.Collections; using System.Globalization; using System.Reflection; using System.Resources; using System.Xml; /// /// Message files generation class. /// public class GenerateMessageFiles { /// /// Generate the message files. /// /// Input Xml document containing message definitions. /// CodeDom container. /// Writer for default resource file. public static void Generate(XmlDocument messagesDoc, CodeCompileUnit codeCompileUnit, ResourceWriter resourceWriter) { Hashtable usedNumbers = new Hashtable(); if (null == messagesDoc) { throw new ArgumentNullException("messagesDoc"); } if (null == codeCompileUnit) { throw new ArgumentNullException("codeCompileUnit"); } if (null == resourceWriter) { throw new ArgumentNullException("resourceWriter"); } string namespaceAttr = messagesDoc.DocumentElement.GetAttribute("Namespace"); string resourcesAttr = messagesDoc.DocumentElement.GetAttribute("Resources"); // namespace CodeNamespace messagesNamespace = new CodeNamespace(namespaceAttr); codeCompileUnit.Namespaces.Add(messagesNamespace); // imports messagesNamespace.Imports.Add(new CodeNamespaceImport("System")); messagesNamespace.Imports.Add(new CodeNamespaceImport("System.Reflection")); messagesNamespace.Imports.Add(new CodeNamespaceImport("System.Resources")); if (namespaceAttr != "WixToolset.Data") { messagesNamespace.Imports.Add(new CodeNamespaceImport("WixToolset.Data")); } foreach (XmlElement classElement in messagesDoc.DocumentElement.ChildNodes) { string className = classElement.GetAttribute("Name"); string baseContainerName = classElement.GetAttribute("BaseContainerName"); string containerName = classElement.GetAttribute("ContainerName"); string messageLevel = classElement.GetAttribute("Level"); // message container class messagesNamespace.Types.Add(CreateContainer(namespaceAttr, baseContainerName, containerName, messageLevel, resourcesAttr)); // class CodeTypeDeclaration messagesClass = new CodeTypeDeclaration(className); messagesClass.TypeAttributes = TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed; messagesNamespace.Types.Add(messagesClass); // private constructor (needed since all methods in this class are static) CodeConstructor constructor = new CodeConstructor(); constructor.Attributes = MemberAttributes.Private; constructor.ReturnType = null; messagesClass.Members.Add(constructor); // messages foreach (XmlElement messageElement in classElement.ChildNodes) { int number; string id = messageElement.GetAttribute("Id"); string numberString = messageElement.GetAttribute("Number"); bool sourceLineNumbers = true; // determine the message number (and ensure it was set properly) if (0 < numberString.Length) { number = Convert.ToInt32(numberString, CultureInfo.InvariantCulture); } else { throw new ApplicationException(String.Format("Message number must be assigned for {0} '{1}'.", containerName, id)); } // check for message number collisions if (usedNumbers.Contains(number)) { throw new ApplicationException(String.Format("Collision detected between two or more messages with number '{0}'.", number)); } usedNumbers.Add(number, null); if ("no" == messageElement.GetAttribute("SourceLineNumbers")) { sourceLineNumbers = false; } int instanceCount = 0; foreach (XmlElement instanceElement in messageElement.ChildNodes) { string formatString = instanceElement.InnerText.Trim(); string resourceName = String.Concat(className, "_", id, "_", (++instanceCount).ToString()); // create a resource resourceWriter.AddResource(resourceName, formatString); // create method CodeMemberMethod method = new CodeMemberMethod(); method.ReturnType = new CodeTypeReference(baseContainerName); method.Attributes = MemberAttributes.Public | MemberAttributes.Static; messagesClass.Members.Add(method); // method name method.Name = id; // return statement CodeMethodReturnStatement stmt = new CodeMethodReturnStatement(); method.Statements.Add(stmt); // return statement expression CodeObjectCreateExpression expr = new CodeObjectCreateExpression(); stmt.Expression = expr; // new struct expr.CreateType = new CodeTypeReference(containerName); // optionally have sourceLineNumbers as the first parameter if (sourceLineNumbers) { // sourceLineNumbers parameter expr.Parameters.Add(new CodeArgumentReferenceExpression("sourceLineNumbers")); } else { expr.Parameters.Add(new CodePrimitiveExpression(null)); } // message number parameter expr.Parameters.Add(new CodePrimitiveExpression(number)); // resource name parameter expr.Parameters.Add(new CodePrimitiveExpression(resourceName)); // optionally have sourceLineNumbers as the first parameter if (sourceLineNumbers) { method.Parameters.Add(new CodeParameterDeclarationExpression("SourceLineNumber", "sourceLineNumbers")); } foreach (XmlNode parameterNode in instanceElement.ChildNodes) { XmlElement parameterElement; if (null != (parameterElement = parameterNode as XmlElement)) { string type = parameterElement.GetAttribute("Type"); string name = parameterElement.GetAttribute("Name"); // method parameter method.Parameters.Add(new CodeParameterDeclarationExpression(type, name)); // String.Format parameter expr.Parameters.Add(new CodeArgumentReferenceExpression(name)); } } } } } } /// /// Create message container class. /// /// Namespace to use for resources stream. /// Name of the base message container class. /// Name of the message container class. /// Message level of for the message. /// Name of the resources stream (will get namespace prepended). /// Message container class CodeDom object. private static CodeTypeDeclaration CreateContainer(string namespaceName, string baseContainerName, string containerName, string messageLevel, string resourcesName) { CodeTypeDeclaration messageContainer = new CodeTypeDeclaration(); messageContainer.Name = containerName; messageContainer.BaseTypes.Add(new CodeTypeReference(baseContainerName)); // constructor CodeConstructor constructor = new CodeConstructor(); constructor.Attributes = MemberAttributes.Public; constructor.ReturnType = null; messageContainer.Members.Add(constructor); CodeMemberField resourceManager = new CodeMemberField(); resourceManager.Attributes = MemberAttributes.Private | MemberAttributes.Static; resourceManager.Name = "resourceManager"; resourceManager.Type = new CodeTypeReference("ResourceManager"); resourceManager.InitExpression = new CodeObjectCreateExpression("ResourceManager", new CodeSnippetExpression(String.Format("\"{0}.{1}\"", namespaceName, resourcesName)), new CodeSnippetExpression("Assembly.GetExecutingAssembly()")); messageContainer.Members.Add(resourceManager); // constructor parameters constructor.Parameters.Add(new CodeParameterDeclarationExpression("SourceLineNumber", "sourceLineNumbers")); constructor.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "id")); constructor.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "resourceName")); CodeParameterDeclarationExpression messageArgsParam = new CodeParameterDeclarationExpression("params object[]", "messageArgs"); constructor.Parameters.Add(messageArgsParam); constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("sourceLineNumbers")); constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("id")); constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("resourceName")); constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("messageArgs")); // assign base.Level if messageLevel is specified if (!String.IsNullOrEmpty(messageLevel)) { CodePropertyReferenceExpression levelReference = new CodePropertyReferenceExpression(new CodeBaseReferenceExpression(), "Level"); CodeFieldReferenceExpression messageLevelField = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("MessageLevel"), messageLevel); constructor.Statements.Add(new CodeAssignStatement(levelReference, messageLevelField)); } // Assign base.ResourceManager property CodePropertyReferenceExpression baseResourceManagerReference = new CodePropertyReferenceExpression(new CodeBaseReferenceExpression(), "ResourceManager"); CodeFieldReferenceExpression resourceManagerField = new CodeFieldReferenceExpression(null, "resourceManager"); constructor.Statements.Add(new CodeAssignStatement(baseResourceManagerReference, resourceManagerField)); //CodeMemberProperty resourceManagerProperty = new CodeMemberProperty(); //resourceManagerProperty.Attributes = MemberAttributes.Public | MemberAttributes.Override; //resourceManagerProperty.Name = "ResourceManager"; //resourceManagerProperty.Type = new CodeTypeReference("ResourceManager"); //CodeFieldReferenceExpression resourceManagerReference = new CodeFieldReferenceExpression(); //resourceManagerReference.FieldName = "resourceManager"; //resourceManagerProperty.GetStatements.Add(new CodeMethodReturnStatement(resourceManagerReference)); //messageContainer.Members.Add(resourceManagerProperty); return messageContainer; } } }