//
// mono-api-info.cs - Dumps public assembly information to an xml file.
//
// Authors:
//	Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// Copyright (C) 2003-2005 Novell, Inc (http://www.novell.com)
//

using System;
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Xml;

namespace Mono.AssemblyInfo
{
	class Driver
	{
		static int Main (string [] args)
		{
			if (args.Length == 0)
				return 1;

			AssemblyCollection acoll = new AssemblyCollection ();
			
			foreach (string fullName in args) {
				acoll.Add (fullName);
			}

			XmlDocument doc = new XmlDocument ();
			acoll.Document = doc;
			acoll.DoOutput ();

			XmlTextWriter writer = new XmlTextWriter (Console.Out);
			writer.Formatting = Formatting.Indented;
			XmlNode decl = doc.CreateXmlDeclaration ("1.0", null, null);
			doc.InsertBefore (decl, doc.DocumentElement);
			doc.WriteTo (writer);
			return 0;
		}
	}

	class AssemblyCollection
	{
		XmlDocument document;
		ArrayList assemblies;

		public AssemblyCollection ()
		{
			assemblies = new ArrayList ();
		}

		public bool Add (string name)
		{
			Assembly ass = LoadAssembly (name);
			if (ass == null)
				return false;

			assemblies.Add (ass);
			return true;
		}

		public void DoOutput ()
		{
			if (document == null)
				throw new InvalidOperationException ("Document not set");

			XmlNode nassemblies = document.CreateElement ("assemblies", null);
			document.AppendChild (nassemblies);
			foreach (Assembly a in assemblies) {
				AssemblyData data = new AssemblyData (document, nassemblies, a);
				data.DoOutput ();
			}
		}

		public XmlDocument Document {
			set { document = value; }
		}
		
		static Assembly LoadAssembly (string aname)
		{
			Assembly ass = null;
			try {
				string name = aname;
				if (!name.EndsWith (".dll"))
					name += ".dll";
				ass = Assembly.LoadFrom (name);
				return ass;
			} catch { }

			try {
				ass = Assembly.LoadWithPartialName (aname);
				return ass;
			} catch { }

			return null;
		}
	}

	abstract class BaseData
	{
		protected XmlDocument document;
		protected XmlNode parent;

		protected BaseData (XmlDocument doc, XmlNode parent)
		{
			this.document = doc;
			this.parent = parent;
		}

		public abstract void DoOutput ();

		protected void AddAttribute (XmlNode node, string name, string value)
		{
			XmlAttribute attr = document.CreateAttribute (name);
			attr.Value = value;
			node.Attributes.Append (attr);
		}
	}

	class AssemblyData : BaseData
	{
		Assembly ass;
		
		public AssemblyData (XmlDocument document, XmlNode parent, Assembly ass)
			: base (document, parent)
		{
			this.ass = ass;
		}

		public override void DoOutput ()
		{
			if (document == null)
				throw new InvalidOperationException ("Document not set");

			XmlNode nassembly = document.CreateElement ("assembly", null);
			AssemblyName aname = ass.GetName ();
			AddAttribute (nassembly, "name", aname.Name);
			AddAttribute (nassembly, "version", aname.Version.ToString ());
			parent.AppendChild (nassembly);
			AttributeData.OutputAttributes (document, nassembly, ass.GetCustomAttributes (false));
			Type [] types = ass.GetExportedTypes ();
			if (types == null || types.Length == 0)
				return;

			Array.Sort (types, TypeComparer.Default);

			XmlNode nss = document.CreateElement ("namespaces", null);
			nassembly.AppendChild (nss);

			string currentNS = "$%&$&";
			XmlNode ns = null;
			XmlNode classes = null;
			foreach (Type t in types) {
				if (t.Namespace == null || t.Namespace == "")
					continue;

				if (t.IsNotPublic)
					continue;

				if (t.IsNestedPublic || t.IsNestedAssembly || t.IsNestedFamANDAssem ||
					t.IsNestedFamORAssem || t.IsNestedPrivate)
					continue;

				if (t.DeclaringType != null)
					continue; // enforce !nested
				
				if (t.Namespace != currentNS) {
					currentNS = t.Namespace;
					ns = document.CreateElement ("namespace", null);
					AddAttribute (ns, "name", currentNS);
					nss.AppendChild (ns);
					classes = document.CreateElement ("classes", null);
					ns.AppendChild (classes);
				}
				
				TypeData bd = new TypeData (document, classes, t);
				bd.DoOutput ();
			}
		}
	}

	abstract class MemberData : BaseData
	{
		MemberInfo [] members;

		public MemberData (XmlDocument document, XmlNode parent, MemberInfo [] members)
			: base (document, parent)
		{
			this.members = members;
		}

		public override void DoOutput ()
		{
			XmlNode mclass = document.CreateElement (ParentTag, null);
			parent.AppendChild (mclass);

			foreach (MemberInfo member in members) {
				XmlNode mnode = document.CreateElement (Tag, null);
				mclass.AppendChild (mnode);
				AddAttribute (mnode, "name", GetName (member));
				if (!NoMemberAttributes)
					AddAttribute (mnode, "attrib", GetMemberAttributes (member));

				AttributeData.OutputAttributes (document, mnode,
								member.GetCustomAttributes (false));

				AddExtraData (mnode, member);
			}
		}

		protected virtual void AddExtraData (XmlNode p, MemberInfo member)
		{
		}

		protected virtual string GetName (MemberInfo member)
		{
			return "NoNAME";
		}

		protected virtual string GetMemberAttributes (MemberInfo member)
		{
			return null;
		}

		public virtual bool NoMemberAttributes {
			get { return false; }
			set {}
		}

		public virtual string ParentTag {
			get { return "NoPARENTTAG"; }
		}
		
		public virtual string Tag {
			get { return "NoTAG"; }
		}
	}

	class TypeData : MemberData
	{
		Type type;
		const BindingFlags flags = BindingFlags.Public | BindingFlags.Static |
						BindingFlags.Instance | BindingFlags.DeclaredOnly | 
						BindingFlags.NonPublic;
		
		public TypeData (XmlDocument document, XmlNode parent, Type type)
			: base (document, parent, null)
		{
			this.type = type;
		}

		public override void DoOutput ()
		{
			if (document == null)
				throw new InvalidOperationException ("Document not set");

			XmlNode nclass = document.CreateElement ("class", null);
			AddAttribute (nclass, "name", type.Name);
			string classType = GetClassType (type);
			AddAttribute (nclass, "type", classType);

			if (type.BaseType != null)
				AddAttribute (nclass, "base", type.BaseType.ToString ());

			if (type.IsSealed)
				AddAttribute (nclass, "sealed", "true");

			if (type.IsAbstract)
				AddAttribute (nclass, "abstract", "true");

			if (type.IsSerializable)
				AddAttribute (nclass, "serializable", "true");

			string charSet = GetCharSet (type);
			AddAttribute (nclass, "charset", charSet);

			string layout = GetLayout (type);
			if (layout != null)
				AddAttribute (nclass, "layout", layout);

			parent.AppendChild (nclass);
			
			AttributeData.OutputAttributes (document, nclass, type.GetCustomAttributes (false));

			Type [] interfaces = type.GetInterfaces ();
			if (interfaces != null && interfaces.Length > 0) {
				XmlNode ifaces = document.CreateElement ("interfaces", null);
				nclass.AppendChild (ifaces);
				foreach (Type t in interfaces) {
					if (!t.IsPublic) {
						// we're only interested in public interfaces
						continue;
					}
					XmlNode iface = document.CreateElement ("interface", null);
					AddAttribute (iface, "name", t.ToString ());
					ifaces.AppendChild (iface);
				}
			}

#if NET_2_0
			// Generic constraints
			Type [] gargs = type.GetGenericArguments ();
			XmlElement ngeneric = (gargs.Length == 0) ? null :
				document.CreateElement ("generic-type-constraints");
			foreach (Type garg in gargs) {
				Type [] csts = garg.GetGenericParameterConstraints ();
				if (csts.Length == 0 || csts [0] == typeof (object))
					continue;
				XmlElement el = document.CreateElement ("generic-type-constraint");
				el.SetAttribute ("name", garg.ToString ());
				el.SetAttribute ("generic-attribute",
					garg.GenericParameterAttributes.ToString ());
				ngeneric.AppendChild (el);
				foreach (Type ct in csts) {
					XmlElement cel = document.CreateElement ("type");
					cel.AppendChild (document.CreateTextNode (ct.FullName));
					el.AppendChild (cel);
				}
			}
			if (ngeneric != null && ngeneric.FirstChild != null)
				nclass.AppendChild (ngeneric);
#endif

			ArrayList members = new ArrayList ();

			FieldInfo[] fields = GetFields (type);
			if (fields.Length > 0) {
				Array.Sort (fields, MemberInfoComparer.Default);
				FieldData fd = new FieldData (document, nclass, fields);
				// Special case for enum fields
				if (classType == "enum") {
					string etype = fields [0].GetType ().ToString ();
					AddAttribute (nclass, "enumtype", etype);
				}
				members.Add (fd);
			}

			ConstructorInfo [] ctors = GetConstructors (type);
			if (ctors.Length > 0) {
				Array.Sort (ctors, MemberInfoComparer.Default);
				members.Add (new ConstructorData (document, nclass, ctors));
			}

			PropertyInfo[] properties = GetProperties (type);
			if (properties.Length > 0) {
				Array.Sort (properties, MemberInfoComparer.Default);
				members.Add (new PropertyData (document, nclass, properties));
			}

			EventInfo [] events = GetEvents (type);
			if (events.Length > 0) {
				Array.Sort (events, MemberInfoComparer.Default);
				members.Add (new EventData (document, nclass, events));
			}

			MethodInfo [] methods = GetMethods (type);
			if (methods.Length > 0) {
				Array.Sort (methods, MemberInfoComparer.Default);
				members.Add (new MethodData (document, nclass, methods));
			}

			foreach (MemberData md in members)
				md.DoOutput ();

			Type [] nested = type.GetNestedTypes ();
			if (nested != null && nested.Length > 0) {
				XmlNode classes = document.CreateElement ("classes", null);
				nclass.AppendChild (classes);
				foreach (Type t in nested) {
					TypeData td = new TypeData (document, classes, t);
					td.DoOutput ();
				}
			}
		}

		protected override string GetMemberAttributes (MemberInfo member)
		{
			if (member != type)
				throw new InvalidOperationException ("odd");
				
			return ((int) type.Attributes).ToString (CultureInfo.InvariantCulture);
		}

		public static bool MustDocumentMethod(MethodBase method)
		{
			// All other methods
			return (method.IsPublic || method.IsFamily || method.IsFamilyOrAssembly);
		}

		static string GetClassType (Type t)
		{
			if (t.IsEnum)
				return "enum";

			if (t.IsValueType)
				return "struct";

			if (t.IsInterface)
				return "interface";

			if (typeof (Delegate).IsAssignableFrom (t))
				return "delegate";

			return "class";
		}

		private static string GetCharSet (Type type)
		{
			if (type.IsAnsiClass)
				return CharSet.Ansi.ToString (CultureInfo.InvariantCulture);

			if (type.IsAutoClass)
				return CharSet.Auto.ToString (CultureInfo.InvariantCulture);

			if (type.IsUnicodeClass)
				return CharSet.Unicode.ToString (CultureInfo.InvariantCulture);

			return CharSet.None.ToString (CultureInfo.InvariantCulture);
		}

		private static string GetLayout (Type type)
		{
			if (type.IsAutoLayout)
				return LayoutKind.Auto.ToString (CultureInfo.InvariantCulture);

			if (type.IsExplicitLayout)
				return LayoutKind.Explicit.ToString (CultureInfo.InvariantCulture);

			if (type.IsLayoutSequential)
				return LayoutKind.Sequential.ToString (CultureInfo.InvariantCulture);

			return null;
		}

		private FieldInfo[] GetFields (Type type)
		{
			ArrayList list = new ArrayList ();

			FieldInfo[] fields = type.GetFields (flags);
			foreach (FieldInfo field in fields) {
				if (field.IsSpecialName)
					continue;

				// we're only interested in public or protected members
				if (!field.IsPublic && !field.IsFamily && !field.IsFamilyOrAssembly)
					continue;

				list.Add (field);
			}

			return (FieldInfo[]) list.ToArray (typeof (FieldInfo));
		}

		internal static PropertyInfo[] GetProperties (Type type)
		{
			ArrayList list = new ArrayList ();

			PropertyInfo[] properties = type.GetProperties (flags);
			foreach (PropertyInfo property in properties) {
				MethodInfo getMethod = null;
				MethodInfo setMethod = null;

				if (property.CanRead) {
					try { getMethod = property.GetGetMethod (true); }
					catch (System.Security.SecurityException) { }
				}
				if (property.CanWrite) {
					try { setMethod = property.GetSetMethod (true); }
					catch (System.Security.SecurityException) { }
				}

				bool hasGetter = (getMethod != null) && MustDocumentMethod (getMethod);
				bool hasSetter = (setMethod != null) && MustDocumentMethod (setMethod);

				// if neither the getter or setter should be documented, then
				// skip the property
				if (!hasGetter && !hasSetter) {
					continue;
				}

				list.Add (property);
			}

			return (PropertyInfo[]) list.ToArray (typeof (PropertyInfo));
		}

		private MethodInfo[] GetMethods (Type type)
		{
			ArrayList list = new ArrayList ();

			MethodInfo[] methods = type.GetMethods (flags);
			foreach (MethodInfo method in methods) {
				if (method.IsSpecialName)
					continue;

				// we're only interested in public or protected members
				if (!MustDocumentMethod(method))
					continue;

				list.Add (method);
			}

			return (MethodInfo[]) list.ToArray (typeof (MethodInfo));
		}

		private ConstructorInfo[] GetConstructors (Type type)
		{
			ArrayList list = new ArrayList ();

			ConstructorInfo[] ctors = type.GetConstructors (flags);
			foreach (ConstructorInfo constructor in ctors) {
				// we're only interested in public or protected members
				if (!constructor.IsPublic && !constructor.IsFamily && !constructor.IsFamilyOrAssembly)
					continue;

				list.Add (constructor);
			}

			return (ConstructorInfo[]) list.ToArray (typeof (ConstructorInfo));
		}

		private EventInfo[] GetEvents (Type type)
		{
			ArrayList list = new ArrayList ();

			EventInfo[] events = type.GetEvents (flags);
			foreach (EventInfo eventInfo in events) {
				MethodInfo addMethod = eventInfo.GetAddMethod (true);

				if (addMethod == null || !MustDocumentMethod (addMethod))
					continue;

				list.Add (eventInfo);
			}

			return (EventInfo[]) list.ToArray (typeof (EventInfo));
		}
	}

	class FieldData : MemberData
	{
		public FieldData (XmlDocument document, XmlNode parent, FieldInfo [] members)
			: base (document, parent, members)
		{
		}

		protected override string GetName (MemberInfo member)
		{
			FieldInfo field = (FieldInfo) member;
			return field.Name;
		}

		protected override string GetMemberAttributes (MemberInfo member)
		{
			FieldInfo field = (FieldInfo) member;
			return ((int) field.Attributes).ToString (CultureInfo.InvariantCulture);
		}

		protected override void AddExtraData (XmlNode p, MemberInfo member)
		{
			base.AddExtraData (p, member);
			FieldInfo field = (FieldInfo) member;
			AddAttribute (p, "fieldtype", field.FieldType.ToString ());

			if (field.IsLiteral) {
				object value = field.GetValue (null);
				string stringValue = null;
				if (value is Enum) {
					// FIXME: when Mono bug #60090 has been
					// fixed, we should just be able to use
					// Convert.ToString
					stringValue = ((Enum) value).ToString ("D", CultureInfo.InvariantCulture);
				} else {
					stringValue = Convert.ToString (value, CultureInfo.InvariantCulture);
				}

				if (stringValue != null)
					AddAttribute (p, "value", stringValue);
			}
		}

		public override string ParentTag {
			get { return "fields"; }
		}

		public override string Tag {
			get { return "field"; }
		}
	}

	class PropertyData : MemberData
	{
		public PropertyData (XmlDocument document, XmlNode parent, PropertyInfo [] members)
			: base (document, parent, members)
		{
		}

		protected override string GetName (MemberInfo member)
		{
			PropertyInfo prop = (PropertyInfo) member;
			return prop.Name;
		}

		protected override void AddExtraData (XmlNode p, MemberInfo member)
		{
			base.AddExtraData (p, member);
			PropertyInfo prop = (PropertyInfo) member;
			Type t = prop.PropertyType;
			AddAttribute (p, "ptype", prop.PropertyType.ToString ());
			MethodInfo _get = prop.GetGetMethod (true);
			MethodInfo _set = prop.GetSetMethod (true);
			bool haveGet = (_get != null && TypeData.MustDocumentMethod(_get));
			bool haveSet = (_set != null && TypeData.MustDocumentMethod(_set));
			MethodInfo [] methods;

			if (haveGet && haveSet) {
				methods = new MethodInfo [] {_get, _set};
			} else if (haveGet) {
				methods = new MethodInfo [] {_get};
			} else if (haveSet) {
				methods = new MethodInfo [] {_set};
			} else {
				//odd
				return;
			}

			string parms = Parameters.GetSignature (methods [0].GetParameters ());
			AddAttribute (p, "params", parms);

			MethodData data = new MethodData (document, p, methods);
			//data.NoMemberAttributes = true;
			data.DoOutput ();
		}

		protected override string GetMemberAttributes (MemberInfo member)
		{
			PropertyInfo prop = (PropertyInfo) member;
			return ((int) prop.Attributes & (0xFFFFFFFF ^ (int) PropertyAttributes.ReservedMask)).ToString (CultureInfo.InvariantCulture);
		}

		public override string ParentTag {
			get { return "properties"; }
		}

		public override string Tag {
			get { return "property"; }
		}
	}

	class EventData : MemberData
	{
		public EventData (XmlDocument document, XmlNode parent, EventInfo [] members)
			: base (document, parent, members)
		{
		}

		protected override string GetName (MemberInfo member)
		{
			EventInfo evt = (EventInfo) member;
			return evt.Name;
		}

		protected override string GetMemberAttributes (MemberInfo member)
		{
			EventInfo evt = (EventInfo) member;
			return ((int) evt.Attributes).ToString (CultureInfo.InvariantCulture);
		}

		protected override void AddExtraData (XmlNode p, MemberInfo member)
		{
			base.AddExtraData (p, member);
			EventInfo evt = (EventInfo) member;
			AddAttribute (p, "eventtype", evt.EventHandlerType.ToString ());
		}

		public override string ParentTag {
			get { return "events"; }
		}

		public override string Tag {
			get { return "event"; }
		}
	}

	class MethodData : MemberData
	{
		bool noAtts;

		public MethodData (XmlDocument document, XmlNode parent, MethodBase [] members)
			: base (document, parent, members)
		{
		}

		protected override string GetName (MemberInfo member)
		{
			MethodBase method = (MethodBase) member;
			string name = method.Name;
			string parms = Parameters.GetSignature (method.GetParameters ());
#if NET_2_0
			MethodInfo mi = method as MethodInfo;
			Type [] genArgs = mi == null ? Type.EmptyTypes :
				mi.GetGenericArguments ();
			if (genArgs.Length > 0) {
				string [] genArgNames = new string [genArgs.Length];
				for (int i = 0; i < genArgs.Length; i++) {
					genArgNames [i] = genArgs [i].Name;
					string genArgCsts = String.Empty;
					Type [] gcs = genArgs [i].GetGenericParameterConstraints ();
					if (gcs.Length > 0) {
						string [] gcNames = new string [gcs.Length];
						for (int g = 0; g < gcs.Length; g++)
							gcNames [g] = gcs [g].FullName;
						genArgCsts = String.Concat (
							"(",
							string.Join (", ", gcNames),
							") ",
							genArgNames [i]);
					}
					else
						genArgCsts = genArgNames [i];
					if ((genArgs [i].GenericParameterAttributes & GenericParameterAttributes.ReferenceTypeConstraint) != 0)
						genArgCsts = "class " + genArgCsts;
					else if ((genArgs [i].GenericParameterAttributes & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
						genArgCsts = "struct " + genArgCsts;
					genArgNames [i] = genArgCsts;
				}
				return String.Format ("{0}<{2}>({1})",
					name,
					parms,
					string.Join (",", genArgNames));
			}
#endif
			return String.Format ("{0}({1})", name, parms);
		}

		protected override string GetMemberAttributes (MemberInfo member)
		{
			MethodBase method = (MethodBase) member;
			return ((int)( method.Attributes & ~MethodAttributes.ReservedMask)).ToString (CultureInfo.InvariantCulture);
		}

		protected override void AddExtraData (XmlNode p, MemberInfo member)
		{
			base.AddExtraData (p, member);

			ParameterData parms = new ParameterData (document, p, 
				((MethodBase) member).GetParameters ());
			parms.DoOutput ();

			if (!(member is MethodBase))
				return;

			MethodBase mbase = (MethodBase) member;

			if (mbase.IsAbstract)
				AddAttribute (p, "abstract", "true");
			if (mbase.IsVirtual)
				AddAttribute (p, "virtual", "true");
			if (mbase.IsStatic)
				AddAttribute (p, "static", "true");

			if (!(member is MethodInfo))
				return;

			MethodInfo method = (MethodInfo) member;
			AddAttribute (p, "returntype", method.ReturnType.ToString ());

			AttributeData.OutputAttributes (document, p,
				method.ReturnTypeCustomAttributes.GetCustomAttributes (false));
#if NET_2_0
			// Generic constraints
			Type [] gargs = method.GetGenericArguments ();
			XmlElement ngeneric = (gargs.Length == 0) ? null :
				document.CreateElement ("generic-method-constraints");
			foreach (Type garg in gargs) {
				Type [] csts = garg.GetGenericParameterConstraints ();
				if (csts.Length == 0 || csts [0] == typeof (object))
					continue;
				XmlElement el = document.CreateElement ("generic-method-constraint");
				el.SetAttribute ("name", garg.ToString ());
				el.SetAttribute ("generic-attribute",
					garg.GenericParameterAttributes.ToString ());
				ngeneric.AppendChild (el);
				foreach (Type ct in csts) {
					XmlElement cel = document.CreateElement ("type");
					cel.AppendChild (document.CreateTextNode (ct.FullName));
					el.AppendChild (cel);
				}
			}
			if (ngeneric != null && ngeneric.FirstChild != null)
				p.AppendChild (ngeneric);
#endif

		}

		public override bool NoMemberAttributes {
			get { return noAtts; }
			set { noAtts = value; }
		}
		
		public override string ParentTag {
			get { return "methods"; }
		}

		public override string Tag {
			get { return "method"; }
		}
	}

	class ConstructorData : MethodData
	{
		public ConstructorData (XmlDocument document, XmlNode parent, ConstructorInfo [] members)
			: base (document, parent, members)
		{
		}

		public override string ParentTag {
			get { return "constructors"; }
		}

		public override string Tag {
			get { return "constructor"; }
		}
	}

	class ParameterData : BaseData
	{
		private ParameterInfo[] parameters;

		public ParameterData (XmlDocument document, XmlNode parent, ParameterInfo[] parameters)
			: base (document, parent)
		{
			this.parameters = parameters;
		}

		public override void DoOutput ()
		{
			XmlNode parametersNode = document.CreateElement ("parameters", null);
			parent.AppendChild (parametersNode);

			foreach (ParameterInfo parameter in parameters) {
				XmlNode paramNode = document.CreateElement ("parameter", null);
				parametersNode.AppendChild (paramNode);
				AddAttribute (paramNode, "name", parameter.Name);
				AddAttribute (paramNode, "position", parameter.Position.ToString(CultureInfo.InvariantCulture));
				AddAttribute (paramNode, "attrib", ((int) parameter.Attributes).ToString());

				string direction = "in";

				if (parameter.ParameterType.IsByRef) {
					direction = parameter.IsOut ? "out" : "ref";
				}

				Type t = parameter.ParameterType;
				AddAttribute (paramNode, "type", t.ToString ());

				if (parameter.IsOptional) {
					AddAttribute (paramNode, "optional", "true");
					if (parameter.DefaultValue != System.DBNull.Value)
						AddAttribute (paramNode, "defaultValue", (parameter.DefaultValue == null) ? "NULL" : parameter.DefaultValue.ToString ());
				}

				if (direction != "in")
					AddAttribute (paramNode, "direction", direction);

				AttributeData.OutputAttributes (document, paramNode, parameter.GetCustomAttributes (false));
			}
		}
	}

	class AttributeData : BaseData
	{
		object [] atts;

		AttributeData (XmlDocument doc, XmlNode parent, object[] attributes)
			: base (doc, parent)
		{
			atts = attributes;
		}

		public override void DoOutput ()
		{
			if (document == null)
				throw new InvalidOperationException ("Document not set");

			if (atts == null || atts.Length == 0)
				return;

			XmlNode natts = parent.SelectSingleNode("attributes");
			if (natts == null) {
				natts = document.CreateElement ("attributes", null);
				parent.AppendChild (natts);
			}

			for (int i = 0; i < atts.Length; ++i) {
				Type t = atts [i].GetType ();
				if (!t.IsPublic && !t.Name.EndsWith ("TODOAttribute"))
					continue;

				// we ignore attributes that inherit from SecurityAttribute on purpose as they:
				// * aren't part of GetCustomAttributes in Fx 1.0/1.1;
				// * are encoded differently and in a different metadata table; and
				// * won't ever exactly match MS implementation (from a syntax pov)
				if (t.IsSubclassOf (typeof (SecurityAttribute)))
					continue;

				XmlNode node = document.CreateElement ("attribute");
				AddAttribute (node, "name", t.ToString ());

				XmlNode properties = null;
				foreach (PropertyInfo pi in TypeData.GetProperties (t)) {
					if (pi.Name == "TypeId")
						continue;

					if (properties == null) {
						properties = node.AppendChild (document.CreateElement ("properties"));
					}

					try {
						object o = pi.GetValue (atts [i], null);

						XmlNode n = properties.AppendChild (document.CreateElement ("property"));
						AddAttribute (n, "name", pi.Name);

						if (o == null) {
							AddAttribute (n, "null", "true");
							continue;
						}

						string value = o.ToString ();
						if (t == typeof (GuidAttribute))
							value = value.ToUpper ();

						AddAttribute (n, "value", value);
					}
					catch (TargetInvocationException) {
						continue;
					}
				}

				natts.AppendChild (node);
			}
		}

		public static void OutputAttributes (XmlDocument doc, XmlNode parent, object[] attributes)
		{
			AttributeData ad = new AttributeData (doc, parent, attributes);
			ad.DoOutput ();
		}

		private static bool MustDocumentAttribute (Type attributeType)
		{
			// only document MonoTODOAttribute and public attributes
			return attributeType.Name.EndsWith ("TODOAttribute") || attributeType.IsPublic;
		}
	}

	class Parameters
	{
		private Parameters () {}

		public static string GetSignature (ParameterInfo [] infos)
		{
			if (infos == null || infos.Length == 0)
				return "";

			StringBuilder sb = new StringBuilder ();
			foreach (ParameterInfo info in infos) {
				string modifier;
				if (info.IsIn)
					modifier = "in ";
				else if (info.IsRetval)
					modifier = "ref ";
				else if (info.IsOut)
					modifier = "out ";
				else
					modifier = "";

				string type_name = info.ParameterType.ToString ().Replace ('<', '[').Replace ('>', ']');
				sb.AppendFormat ("{0}{1}, ", modifier, type_name);
			}

			sb.Length -= 2; // remove ", "
			return sb.ToString ();
		}

	}
	
	class TypeComparer : IComparer
	{
		public static TypeComparer Default = new TypeComparer ();

		public int Compare (object a, object b)
		{
			Type ta = (Type) a;
			Type tb = (Type) b;
			int result = String.Compare (ta.Namespace, tb.Namespace);
			if (result != 0)
				return result;

			return String.Compare (ta.Name, tb.Name);
		}
	}

	class MemberInfoComparer : IComparer
	{
		public static MemberInfoComparer Default = new MemberInfoComparer ();

		public int Compare (object a, object b)
		{
			MemberInfo ma = (MemberInfo) a;
			MemberInfo mb = (MemberInfo) b;
			return String.Compare (ma.Name, mb.Name);
		}
	}

	class MethodBaseComparer : IComparer
	{
		public static MethodBaseComparer Default = new MethodBaseComparer ();

		public int Compare (object a, object b)
		{
			MethodBase ma = (MethodBase) a;
			MethodBase mb = (MethodBase) b;
			int res = String.Compare (ma.Name, mb.Name);
			if (res != 0)
				return res;

			ParameterInfo [] pia = ma.GetParameters ();
			ParameterInfo [] pib = mb.GetParameters ();
			if (pia.Length != pib.Length)
				return pia.Length - pib.Length;

			string siga = Parameters.GetSignature (pia);
			string sigb = Parameters.GetSignature (pib);
			return String.Compare (siga, sigb);
		}
	}
}

