From: Daniel C. \(kzu\) <dca...@us...> - 2004-10-10 15:32:37
|
Update of /cvsroot/mvp-xml/Design/v1/src/CustomTools/SGen In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv11107/v1/src/CustomTools/SGen Added Files: XmlSerializerGenerator.cs Log Message: Class that performs the actual processing and code generation. --- NEW FILE: XmlSerializerGenerator.cs --- #region using using System; using System.Collections; using System.CodeDom; using System.CodeDom.Compiler; using System.IO; using System.Reflection; using System.Text.RegularExpressions; using System.Xml.Serialization; #endregion using namespace Mvp.Xml.Design.CustomTools.SGen { /// <summary> /// Generates source code for custom XmlSerializer for a type. /// </summary> public class XmlSerializerGenerator { #region Consts & Fields // Parses private methods. static Regex PrivateMethods = new Regex(@" # Returned type # (?<return>[^\s]+)\s # Method call start # (?<method> (?<name>Read\d+_ # Object being read # (?<object>[^\(]+))\( # Method arguments # (?<arguments>[^\)]+) # Method call end # \))\s{", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.Multiline); /// <summary> /// The return type of the read method. /// </summary> const string ReadMethodReturnType = "return"; /// <summary> /// Full method signature excluding return type. /// </summary> const string ReadMethodFullSignature = "method"; /// <summary> /// Method name. /// </summary> const string ReadMethodName = "name"; /// <summary> /// Method arguments. /// </summary> const string ReadMethodArguments = "arguments"; /// <summary> /// Name of the object being read. /// </summary> const string ReadMethodObjectName = "object"; /// <summary> /// Removes the type declaration from a list of method arguments retrieved by <see cref="PrivateMethods"/>. /// </summary> static Regex RemoveTypeFromArguments = new Regex(@"[^\s,]+[\s]", RegexOptions.Compiled); static Regex PublicRead = new Regex(@"public object (?<method>Read\d+_(?<object>[^\(]+))", RegexOptions.Compiled | RegexOptions.Multiline); /// <summary> /// The root object name of the element to (de)serialize. /// </summary> const string PublicRootObject = "object"; /// <summary> /// The name of the public read method. /// </summary> const string PublicMethodName = "method"; static Regex PublicWrite = new Regex(@"public void (?<method>Write\d+_(?<object>[^\(]+))", RegexOptions.Compiled | RegexOptions.Multiline); #region Code templates /// <summary> /// Template for the override for each element being read. /// {0}=Return type /// {1}=Method name /// {2}=Delegate name /// {3}=Event name /// {4}=Arguments with type definition /// {5}=Arguments with no type /// </summary> const string TemplateMethodOverride = @" /// <remarks/> protected override {0} {1}({4}) {{ {0} obj = base.{1}({5}); {2} handler = {3}; if (handler != null) handler(obj); return obj; }}"; /// <summary> /// Template for the public Read method for the root object /// in the custom reader firing events. /// {0}=Return type /// {1}=Read method name generated by serializer for root object. /// </summary> const string TemplateReadMethod = @" /// <summary>Reads an object of type {0}.</summary> internal {0} Read() {{ return ({0}) {1}(); }}"; /// <summary> /// Template for the public Write method for the root object /// in the custom writer. /// {0}=Object type name /// {1}=Write method name generated by serializer for root object. /// </summary> const string TemplateWriteMethod = @" /// <summary>Writes an object of type {0}.</summary> internal void Write({0} obj) {{ {1}(obj); }}"; /// <summary> /// Template for custom serializer class members. /// {0}=Name of the serializer class /// {1}=Reader type name /// {2}=Writer type name /// {3}=Root object type /// </summary> const string TemplateCustomSerializer = @" {1} _reader; {2} _writer; /// <summary>Constructs the serializer with default reader and writer instances.</summary> public {0}() {{ }} /// <summary>Constructs the serializer with a pre-built reader.</summary> public {0}({1} reader) {{ _reader = reader; }} /// <summary>Constructs the serializer with a pre-built writer.</summary> public {0}({2} writer) {{ _writer = writer; }} /// <summary>Constructs the serializer with pre-built reader and writer.</summary> public {0}({1} reader, {2} writer) {{ _reader = reader; _writer = writer; }} /// <summary>See <see cref=""XmlSerializer.CreateReader""/>.</summary> protected override XmlSerializationReader CreateReader() {{ if (_reader != null) return _reader; else return new {1}(); }} /// <summary>See <see cref=""XmlSerializer.CreateWriter""/>.</summary> protected override XmlSerializationWriter CreateWriter() {{ if (_writer != null) return _writer; else return new {2}(); }} /// <summary>See <see cref=""XmlSerializer.Deserialize""/>.</summary> protected override object Deserialize(XmlSerializationReader reader) {{ if (!(reader is {1})) throw new ArgumentException(""reader""); return (({1})reader).Read(); }} /// <summary>See <see cref=""XmlSerializer.Serialize""/>.</summary> protected override void Serialize(object o, XmlSerializationWriter writer) {{ if (!(writer is {2})) throw new ArgumentException(""writer""); (({2})writer).Write(({3})o); }}"; #endregion Code templates #endregion Consts & Fields /// <summary> /// Generates the code for a type. /// </summary> /// <param name="type">The type to generate custom XmlSerializer for.</param> /// <param name="targetNamespace">Target namespace to use for the generated classes.</param> /// <returns>Raw C# source code.</returns> public static string GenerateCode(Type type, string targetNamespace) { #region Retrieve XmlSerializer output string output; XmlSerializer ser = new XmlSerializer(type); // Here starts the horrible reflection hacks! //ser.tempAssembly.assembly.Location FieldInfo fitempasm = ser.GetType().GetField("tempAssembly", BindingFlags.NonPublic | BindingFlags.Instance); object tempasm = fitempasm.GetValue(ser); FieldInfo fiasm = tempasm.GetType().GetField("assembly", BindingFlags.NonPublic | BindingFlags.Instance); Assembly asm = (Assembly) fiasm.GetValue(tempasm); string codebase = new Uri(asm.CodeBase).LocalPath; string code = Path.Combine( Path.GetDirectoryName(codebase), Path.GetFileNameWithoutExtension(codebase) + ".0.cs"); using (StreamReader sr = new StreamReader(code)) { output = sr.ReadToEnd(); } #endregion Retrieve XmlSerializer output #region Create CodeDom System.CodeDom.CodeNamespace ns = new System.CodeDom.CodeNamespace(targetNamespace); ns.Imports.Add(new CodeNamespaceImport("System.Xml.Serialization")); ns.Imports.Add(new CodeNamespaceImport("System")); CodeTypeDeclaration serializer = new CodeTypeDeclaration(type.Name + "Serializer"); serializer.BaseTypes.Add(typeof(XmlSerializer)); serializer.Comments.Add(new CodeCommentStatement( "<summary>Custom serializer for " + type.Name + " type.</summary", true)); ns.Types.Add(serializer); #endregion Create CodeDom #region Initial string manipulation & custom XmlSerializer creation // Remove assembly attribute. output = output.Replace("[assembly:System.Security.AllowPartiallyTrustedCallers()]", ""); // Replace namespace. output = output.Replace("Microsoft.Xml.Serialization.GeneratedAssembly", type.FullName + "Serialization"); // Give generated classes more friendly names. output = output.Replace("public class XmlSerializationWriter1", @"/// <remarks/> public class Writer"); output = output.Replace("public class XmlSerializationReader1", @"/// <remarks/> public class Reader"); // Find the method that is the entry point for reading the object. Match readmatch = PublicRead.Match(output); string rootobject = readmatch.Groups[PublicRootObject].Value; // Now add custom serializer members with the new info. string sm = String.Format( TemplateCustomSerializer, serializer.Name, rootobject + "Reader", rootobject + "Writer", type.FullName); serializer.Members.Add(new CodeSnippetTypeMember(sm)); #endregion Initial string manipulation & custom XmlSerializer creation #region Create custom event rising typed reader CodeTypeDeclaration reader = new CodeTypeDeclaration(rootobject + "Reader"); reader.BaseTypes.Add(serializer.Name + ".Reader"); ns.Types.Add(reader); // For each read method Match readm = PrivateMethods.Match(output); while (readm.Success) { string objectread = readm.Groups[ReadMethodObjectName].Value; // Skip the generic reading method. if (objectread.ToLower() == "object") { readm = readm.NextMatch(); continue; } // Create a delegate for the event to expose. CodeTypeDelegate del = new CodeTypeDelegate( objectread + "DeserializedHandler"); char[] name = objectread.ToCharArray(); name[0] = Char.ToLower(name[0]); del.Parameters.Add(new CodeParameterDeclarationExpression( readm.Groups[ReadMethodReturnType].Value, new string(name))); del.Comments.Add(new CodeCommentStatement("/// <remarks/>", true)); ns.Types.Add(del); // Expose event. CodeMemberEvent ev = new CodeMemberEvent(); ev.Name = objectread + "Deserialized"; ev.Attributes = MemberAttributes.Public; ev.Type = new CodeTypeReference(del.Name); ev.Comments.Add(new CodeCommentStatement("/// <remarks/>", true)); reader.Members.Add(ev); // Override base method. string mo = String.Format( TemplateMethodOverride, readm.Groups[ReadMethodReturnType].Value, readm.Groups[ReadMethodName].Value, del.Name, ev.Name, readm.Groups[ReadMethodArguments].Value, // Arguments contain type + parameter name. Remove type for method call. RemoveTypeFromArguments.Replace( readm.Groups[ReadMethodArguments].Value, "")); reader.Members.Add(new CodeSnippetTypeMember(mo)); readm = readm.NextMatch(); } // Add the "final" public read method. reader.Members.Add(new CodeSnippetTypeMember( String.Format(TemplateReadMethod, type.FullName, readmatch.Groups[PublicMethodName].Value))); // Turn original public method into internal protected output = PublicRead.Replace(output, "protected internal object ${method}"); // Turn all private methods into protected virtual, as they are overriden // by the custom event rising reader. output = PrivateMethods.Replace(output, @"/// <remarks/> protected virtual ${return} ${method} {"); #endregion Create custom event rising typed reader #region Create custom writer CodeTypeDeclaration writer = new CodeTypeDeclaration(rootobject + "Writer"); writer.BaseTypes.Add(serializer.Name + ".Writer"); ns.Types.Add(writer); // Find the method that is the entry point for writing the object. Match writematch = PublicWrite.Match(output); // Add the "final" public write method. writer.Members.Add(new CodeSnippetTypeMember( String.Format(TemplateWriteMethod, type.FullName, writematch.Groups[PublicMethodName].Value))); // Turn original public method into internal protected output = PublicWrite.Replace(output, "protected internal void ${method}"); #endregion Create custom writer #region Make generated reader and writer classes inner classes of custom serializer int indexofreader = output.IndexOf("public class Reader"); int indexofwriter = output.IndexOf("public class Writer"); string genw = output.Substring( indexofwriter, indexofreader - indexofwriter); string genr = output.Substring( indexofreader, output.Length - indexofreader - 4); // Add #pragma to avoid compiler warnings. // TODO: Check how to do this for v1.x. //genr = "#pragma warning disable CS0642\n" + genr + "\n#pragma warning restore CS0642"; //genw = "#pragma warning disable CS0642\n" + genw + "\n#pragma warning restore CS0642"; serializer.Members.Add(new CodeSnippetTypeMember(genr)); serializer.Members.Add(new CodeSnippetTypeMember(genw)); #endregion Make generated reader and writer classes inner classes of custom serializer #region Generate code from CodeDom ICodeGenerator gen = new Microsoft.CSharp.CSharpCodeProvider().CreateGenerator(); CodeGeneratorOptions opt = new CodeGeneratorOptions(); opt.BracingStyle = "C"; StringWriter finalcode = new StringWriter(); gen.GenerateCodeFromNamespace(ns, finalcode, opt); #endregion Generate code from CodeDom //output = finalcode.ToString() + output; return finalcode.ToString(); //return output; } } } |