using System;
using System.Collections;
using System.Collections.Specialized;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml.Serialization;
namespace XsdTidy
{
using XsdTidy.Collections;
/// <summary>
/// Transforms the XSD wrapper classes outputed by the Xsd.exe utility
/// to nicer classes using Reflection.
/// </summary>
/// <remarks>
/// </remarks>
public class XsdWrapperGenerator
{
private AssemblyBuilder assembly;
private ModuleBuilder module;
private NameConformer conformer;
private string defaultNamespace;
private TypeTypeBuilderDictionary builders = new TypeTypeBuilderDictionary();
private FieldInfoFieldBuidlerDictionary fieldBuilders = new FieldInfoFieldBuidlerDictionary();
private TypeBuilderConstructorBuilderDictionary contructorBuilders = new TypeBuilderConstructorBuilderDictionary();
private bool alwaysCreateCollections = true;
#region Public Interface
/// <summary>
/// Creates a new wrapper generator.
/// </summary>
/// <param name="name"></param>
public XsdWrapperGenerator(string name, Version version)
{
if (name==null)
throw new ArgumentNullException("mb");
this.defaultNamespace = name;
this.conformer = new NameConformer();
BuildAssembly(name,version);
}
/// <summary>
/// XsdWrapperGenerator
/// </summary>
/// <param name="name">assembly output name (without extension)</param>
/// <param name="conformer">object that takes care of the camel
/// capit consersion</param>
public XsdWrapperGenerator(string name, Version version, NameConformer conformer)
{
if (name==null)
throw new ArgumentNullException("mb");
if (conformer==null)
throw new ArgumentNullException("conformer");
this.defaultNamespace = name;
this.conformer = conformer;
BuildAssembly(name,version);
}
private void BuildAssembly(string name, Version version)
{
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = name;
assemblyName.Version = version;
this.assembly = Thread.GetDomain().DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.Save
);
this.module = assembly.DefineDynamicModule(name + ".dll");
}
/// <summary>
/// Adds a class to the wrapped class list
/// </summary>
/// <param name="t"></param>
public void AddClass(Type t)
{
if (t==null)
throw new ArgumentNullException("t");
if (this.builders.Contains(t))
return;
// create type builder for classType
TypeBuilder typeBuilder = module.DefineType(
this.defaultNamespace + "." + this.conformer.ToCapitalized(t.Name),
TypeAttributes.Public | TypeAttributes.Class
);
// add builder to hashtable
this.builders.Add(t,typeBuilder);
}
/// <summary>
/// Emits wrappers for classes
/// </summary>
public void WrapClasses()
{
OnTypePass();
foreach(DictionaryEntry de in this.builders)
{
WrapClass(
(TypeBuilder)de.Value,
(Type)de.Key
);
}
OnConstructorDefinePass();
// generate default constructors
foreach(DictionaryEntry de in this.builders)
{
// add constructors
GenerateDefaultConstructor(
(TypeBuilder)de.Value,
(Type)de.Key
);
}
// generate properties
OnPropertyPass();
foreach(DictionaryEntry de in this.builders)
{
Type t = (Type)de.Key;
foreach(FieldInfo f in t.GetFields())
{
GenerateProperty(
(TypeBuilder)de.Value,
f);
}
}
// populate default constructors
OnConstructorBuildPass();
foreach(DictionaryEntry de in this.builders)
{
// add constructors
PopulateDefaultConstructor(
(TypeBuilder)de.Value,
(Type)de.Key
);
// generate other constructors
GenerateConstructors(
(TypeBuilder)de.Value,
(Type)de.Key
);
}
}
/// <summary>
/// Saves the generated assembly to file
/// </summary>
public void Save()
{
foreach(TypeBuilder tb in this.builders.Values)
tb.CreateType();
this.assembly.Save(this.module.Name);
this.builders.Clear();
this.module = null;
this.assembly = null;
}
/// <summary>
/// Gets a value indicating if collection members need to be allocated
/// </summary>
public bool AlwaysCreateCollections
{
get
{
return this.alwaysCreateCollections;
}
set
{
this.alwaysCreateCollections = value;
}
}
#endregion
#region Events
public event EventHandler TypePass;
protected void OnTypePass()
{
if (this.TypePass!=null)
TypePass(this,new EventArgs());
}
public event StringEventHandler TypeBuild;
protected void OnTypeBuild(string msg)
{
if (this.TypeBuild!=null)
TypeBuild(this,new StringEventArgs(msg));
}
public event StringEventHandler FieldBuild;
protected void OnFieldBuild(string msg)
{
if (this.FieldBuild!=null)
FieldBuild(this,new StringEventArgs(msg));
}
public event EventHandler PropertyPass;
protected void OnPropertyPass()
{
if (this.PropertyPass!=null)
PropertyPass(this,new EventArgs());
}
public event StringEventHandler PropertyBuild;
protected void OnPropertyBuild(string msg)
{
if (this.PropertyBuild!=null)
PropertyBuild(this,new StringEventArgs(msg));
}
public event EventHandler ConstructorDefinePass;
protected void OnConstructorDefinePass()
{
if (this.ConstructorDefinePass!=null)
ConstructorDefinePass(this,new EventArgs());
}
public event StringEventHandler ConstructorDefine;
protected void OnConstructorDefine(string msg)
{
if (this.ConstructorDefine!=null)
ConstructorDefine(this,new StringEventArgs(msg));
}
public event EventHandler ConstructorBuildPass;
protected void OnConstructorBuildPass()
{
if (this.ConstructorBuildPass!=null)
ConstructorBuildPass(this,new EventArgs());
}
public event StringEventHandler ConstructorBuild;
protected void OnConstructorBuild(string msg)
{
if (this.ConstructorBuild!=null)
ConstructorBuild(this,new StringEventArgs(msg));
}
#endregion
#region Type Creation
/// <summary>
/// Creates a wrapper of type <paramref name="t"/> using
/// <paramref name="tb"/>.
/// </summary>
/// <param name="t"></param>
/// <param name="tb"></param>
private void WrapClass(TypeBuilder tb, Type t)
{
OnTypeBuild(tb.Name);
// set XmlRoot attribute
ConstructorInfo ci = TypeHelper.GetConstructor(
typeof(XmlRootAttribute),typeof(string));
CustomAttributeBuilder xmlRoot = new CustomAttributeBuilder(
ci,
new Object[] {t.Name}
);
tb.SetCustomAttribute(xmlRoot);
// wrap fields of type
foreach(FieldInfo field in t.GetFields())
{
WrapField(tb,field);
}
}
#endregion
#region Constructor Generation
private void GenerateDefaultConstructor(TypeBuilder tb, Type t)
{
OnConstructorDefine(tb.Name);
// get object constructor
ConstructorBuilder ctor = tb.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
Type.EmptyTypes);
this.contructorBuilders.Add(tb,ctor);
}
private void PopulateDefaultConstructor(TypeBuilder tb, Type t)
{
OnConstructorBuild(t.BaseType.Name);
ConstructorInfo objCtor = TypeHelper.GetDefaultConstructor(typeof(Object));
ConstructorBuilder ctor = this.contructorBuilders[tb];
// emiting consturctor IL
ILGenerator ctorIL = ctor.GetILGenerator();
// call base constructor
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, objCtor);
// emit other values
EmitDefaultValues(ctorIL,t.GetFields());
// finished
ctorIL.Emit(OpCodes.Ret);
}
/// <summary>
/// Generate constructor with requested values
/// </summary>
/// <param name="tb"></param>
/// <param name="wrapped"></param>
private void GenerateConstructors(TypeBuilder tb,Type t)
{
// get field types that cannot be null...
ArrayList fields = new ArrayList();
ArrayList defaultFields = new ArrayList();
foreach(FieldInfo f in t.GetFields())
{
if (TypeHelper.IsXmlNullable(f) && !f.FieldType.IsArray)
fields.Add(f);
else
defaultFields.Add(f);
}
if (fields.Count==0)
return;
Type[] ctorParams = new Type[fields.Count];
for(int i = 0;i<fields.Count;++i)
{
FieldInfo fi = (FieldInfo)fields[i];
if (this.builders.Contains(fi.FieldType))
ctorParams[i] = this.builders[fi.FieldType];
else if (this.fieldBuilders.Contains(fi))
ctorParams[i] = this.fieldBuilders[fi].FieldType;
else
ctorParams[i] = fi.FieldType;
}
// get object constructor
ConstructorInfo objCtor = TypeHelper.GetDefaultConstructor(typeof(Object));
ConstructorBuilder ctor = tb.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
ctorParams);
// emiting consturctor IL
ILGenerator ctorIL = ctor.GetILGenerator();
// call base constructor
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, objCtor);
// assign arguments
for(int i = 0;i<fields.Count;++i)
{
FieldInfo f = (FieldInfo)fields[i];
// push wrapped on the stack
ctorIL.Emit(OpCodes.Ldarg_0);
switch(i)
{
case 0:
ctorIL.Emit(OpCodes.Ldarg_1);break;
case 1:
ctorIL.Emit(OpCodes.Ldarg_2);break;
case 2:
ctorIL.Emit(OpCodes.Ldarg_3);break;
default:
// push index on stack
ctorIL.Emit(OpCodes.Ldind_I2,i+1);
// push ith variable on the stack
ctorIL.Emit(OpCodes.Ldarg_S);
break;
}
// assign field
ctorIL.Emit(OpCodes.Stfld, f);
}
// emit other values
EmitDefaultValues(ctorIL,defaultFields);
// finished
ctorIL.Emit(OpCodes.Ret);
}
/// <summary>
/// Emits default values of field
/// </summary>
/// <param name="il"></param>
/// <param name="wrapped"></param>
/// <param name="fields"></param>
internal void EmitDefaultValues(
ILGenerator il,
ICollection fields
)
{
// wrapping fields
int i = 0;
foreach(FieldInfo f in fields)
{
++i;
Type t = f.FieldType;
if (this.builders.Contains(t))
t = this.builders[t];
else if (this.fieldBuilders.Contains(f))
t = this.fieldBuilders[f].FieldType;
if (f.FieldType.IsValueType)
continue;
if (TypeHelper.IsXmlNullable(f)
&& !(f.FieldType.IsArray && this.alwaysCreateCollections)
)
continue;
if (t==typeof(Object))
continue;
il.Emit(OpCodes.Ldarg_0);
// call default constructor
// check if string
CreateNewObject(il,t);
il.Emit(OpCodes.Stfld, f);
}
}
private ConstructorInfo GetDefaultConstructor(Type t)
{
if (t is TypeBuilder)
{
return this.contructorBuilders[(TypeBuilder)t];
}
else
return TypeHelper.GetDefaultConstructor(t);
}
private void CreateNewObject(ILGenerator il, Type t)
{
if (t==typeof(String))
{
il.Emit(OpCodes.Ldstr,"");
}
else
{
ConstructorInfo fCtr = GetDefaultConstructor(t);
il.Emit(OpCodes.Newobj,fCtr);
}
}
#endregion
#region Field and property generation
private void WrapField(TypeBuilder tb,FieldInfo field)
{
if (tb==null)
throw new ArgumentNullException("tb");
if (field==null)
throw new ArgumentNullException("field");
string camel = this.conformer.ToCamel(field.Name);
OnFieldBuild(tb.Name+"."+camel);
// add field
Type wrappedField = null;
if (this.builders.Contains(field.FieldType))
wrappedField=this.builders[field.FieldType];
else
wrappedField=field.FieldType;
// check if type is an array, otherwize replace by collaction
if (wrappedField==typeof(string[]))
wrappedField = typeof(StringCollection);
else if (wrappedField.IsArray)
wrappedField = typeof(ArrayList);
FieldBuilder fb = tb.DefineField(
camel,
wrappedField,
FieldAttributes.Private
);
// add to table
this.fieldBuilders.Add(field,fb);
}
private void GenerateProperty(TypeBuilder tb, FieldInfo field)
{
string capit = this.conformer.ToCapitalized(field.Name);
FieldBuilder fb = this.fieldBuilders[field];
OnPropertyBuild(tb.Name+"."+capit);
// define property
PropertyBuilder p = tb.DefineProperty(
capit,
PropertyAttributes.HasDefault,
fb.FieldType,
Type.EmptyTypes
);
if (this.builders.Contains(field.FieldType))
{
DecorateWrappedType(p,field);
}
else
{
// check if XmlAttributeAttribute or XmlElement are present
Object[] attributes = field.GetCustomAttributes(true);
if (attributes.Length!=0)
{
foreach(Attribute a in field.GetCustomAttributes(true))
{
if (a.GetType()==typeof(XmlElementAttribute))
{
XmlElementAttribute el = (XmlElementAttribute)a;
ConstructorInfo ci = TypeHelper.GetConstructor(
typeof(XmlElementAttribute),typeof(string),typeof(Type));
Type elType = el.Type;
if (elType!=null && this.builders.Contains(elType))
elType = this.builders[elType];
CustomAttributeBuilder xmlEl = new CustomAttributeBuilder(
ci,
new Object[] {el.ElementName,elType}
);
p.SetCustomAttribute(xmlEl);
}
else if (a.GetType()==typeof(XmlAttributeAttribute))
{
ConstructorInfo ci = TypeHelper.GetConstructor(
typeof(XmlAttributeAttribute),typeof(string));
CustomAttributeBuilder xmlEl = new CustomAttributeBuilder(
ci,
new Object[] {field.Name}
);
p.SetCustomAttribute(xmlEl);
}
else if (a.GetType()==typeof(XmlTextAttribute))
{
ConstructorInfo ci = TypeHelper.GetDefaultConstructor(
typeof(XmlTextAttribute));
CustomAttributeBuilder xmlText = new CustomAttributeBuilder(
ci,
new Object[] {}
);
p.SetCustomAttribute(xmlText);
}
}
}
else
{
ConstructorInfo ci = TypeHelper.GetConstructor(
typeof(XmlElementAttribute),typeof(string));
CustomAttributeBuilder xmlAttr = new CustomAttributeBuilder(
ci,
new Object[] {field.Name}
);
p.SetCustomAttribute(xmlAttr);
}
}
// creates get/set methods
p.SetGetMethod(GenerateGetMethod(tb,fb,capit));
p.SetSetMethod(GenerateSetMethod(tb,fb,field,capit));
}
private void DecorateWrappedType(PropertyBuilder p, FieldInfo f)
{
ConstructorInfo ci = TypeHelper.GetConstructor(
typeof(XmlElementAttribute),typeof(string));
CustomAttributeBuilder xmlEl = new CustomAttributeBuilder(
ci,
new Object[] {f.Name}
);
p.SetCustomAttribute(xmlEl);
}
private MethodBuilder GenerateGetMethod(TypeBuilder tb, FieldBuilder fb, string name)
{
// create get method
MethodBuilder gm = tb.DefineMethod("get_"+name,
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
fb.FieldType,
Type.EmptyTypes
);
ILGenerator getIL = gm.GetILGenerator();
LocalBuilder V_0 = getIL.DeclareLocal(typeof(int));
Label ret = getIL.DefineLabel();
// if the value is null, allocate it
getIL.Emit(OpCodes.Ldarg_0);
getIL.Emit(OpCodes.Ldfld,fb);
getIL.Emit(OpCodes.Stloc_0);
getIL.Emit(OpCodes.Br_S,ret);
getIL.MarkLabel(ret);
getIL.Emit(OpCodes.Ldloc_0);
getIL.Emit(OpCodes.Ret);
return gm;
}
private MethodBuilder GenerateSetMethod(
TypeBuilder tb,
FieldBuilder fb,
FieldInfo field,
string name
)
{
// create set method
MethodBuilder setm = tb.DefineMethod("set_" + name,
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
null,
new Type[] { fb.FieldType }
);
ILGenerator setIL = setm.GetILGenerator();
// check if nullable
if (!TypeHelper.IsXmlNullable(field))
{
Label isTrue = setIL.DefineLabel();
setIL.Emit(OpCodes.Ldarg_1);
setIL.Emit(OpCodes.Brtrue_S,isTrue);
setIL.Emit(OpCodes.Newobj, TypeHelper.GetDefaultConstructor(typeof(ArgumentNullException)));
setIL.Emit(OpCodes.Throw);
setIL.MarkLabel(isTrue);
}
setIL.Emit(OpCodes.Ldarg_0);
setIL.Emit(OpCodes.Ldarg_1);
setIL.Emit(OpCodes.Stfld, fb);
setIL.Emit(OpCodes.Ret);
return setm;
}
#endregion
}
}