// // mono-api-diff.cs - Compares 2 xml files produced by mono-api-info and // produces a file suitable to build class status pages. // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // // (C) 2003 Novell, Inc (http://www.novell.com) // using System; using System.Collections; using System.IO; using System.Reflection; using System.Text; using System.Xml; namespace Mono.AssemblyCompare { class Driver { static int Main (string [] args) { if (args.Length != 2) return 1; XMLAssembly ms = CreateXMLAssembly (args [0]); XMLAssembly mono = CreateXMLAssembly (args [1]); XmlDocument doc = ms.CompareAndGetDocument (mono); XmlTextWriter writer = new XmlTextWriter (Console.Out); writer.Formatting = Formatting.Indented; doc.WriteTo (writer); return 0; } static XMLAssembly CreateXMLAssembly (string file) { XmlDocument doc = new XmlDocument (); doc.Load (File.OpenRead (file)); XmlNode node = doc.SelectSingleNode ("/assemblies/assembly"); XMLAssembly result = new XMLAssembly (); try { result.LoadData (node); } catch (Exception e) { Console.Error.WriteLine ("Error loading {0}: {1}\n{2}", file, e.Message, e); Environment.Exit (1); } return result; } } class Counters { public int Present; public int PresentTotal; public int Missing; public int MissingTotal; public int Todo; public int TodoTotal; public int Extra; public int ExtraTotal; public int Warning; public int WarningTotal; public int ErrorTotal; public Counters () { } public void AddPartialToPartial (Counters other) { Present += other.Present; Extra += other.Extra; Missing += other.Missing; Todo += other.Todo; Warning += other.Warning; AddPartialToTotal (other); } public void AddPartialToTotal (Counters other) { PresentTotal += other.Present; ExtraTotal += other.Extra; MissingTotal += other.Missing; TodoTotal += other.Todo; WarningTotal += other.Warning; } public void AddTotalToPartial (Counters other) { Present += other.PresentTotal; Extra += other.ExtraTotal; Missing += other.MissingTotal; Todo += other.TodoTotal; Warning += other.WarningTotal; AddTotalToTotal (other); } public void AddTotalToTotal (Counters other) { PresentTotal += other.PresentTotal; ExtraTotal += other.ExtraTotal; MissingTotal += other.MissingTotal; TodoTotal += other.TodoTotal; WarningTotal += other.WarningTotal; ErrorTotal += other.ErrorTotal; } public int Total { get { return Present + Missing; } } public int AbsTotal { get { return PresentTotal + MissingTotal; } } public int Ok { get { return Present - Todo; } } public int OkTotal { get { return PresentTotal - TodoTotal - ErrorTotal; } } public override string ToString () { StringWriter sw = new StringWriter (); sw.WriteLine ("Present: {0}", Present); sw.WriteLine ("PresentTotal: {0}", PresentTotal); sw.WriteLine ("Missing: {0}", Missing); sw.WriteLine ("MissingTotal: {0}", MissingTotal); sw.WriteLine ("Todo: {0}", Todo); sw.WriteLine ("TodoTotal: {0}", TodoTotal); sw.WriteLine ("Extra: {0}", Extra); sw.WriteLine ("ExtraTotal: {0}", ExtraTotal); sw.WriteLine ("Warning: {0}", Warning); sw.WriteLine ("WarningTotal: {0}", WarningTotal); sw.WriteLine ("ErrorTotal: {0}", ErrorTotal); sw.WriteLine ("--"); return sw.GetStringBuilder ().ToString (); } } abstract class XMLData { protected XmlDocument document; protected Counters counters; bool haveWarnings; public XMLData () { counters = new Counters (); } public virtual void LoadData (XmlNode node) { } protected object [] LoadRecursive (XmlNodeList nodeList, Type type) { ArrayList list = new ArrayList (); foreach (XmlNode node in nodeList) { XMLData data = (XMLData) Activator.CreateInstance (type); data.LoadData (node); list.Add (data); } return (object []) list.ToArray (type); } protected void AddAttribute (XmlNode node, string name, string value) { XmlAttribute attr = document.CreateAttribute (name); attr.Value = value; node.Attributes.Append (attr); } protected void AddExtra (XmlNode node) { //TODO: count all the subnodes? AddAttribute (node, "presence", "extra"); AddAttribute (node, "ok", "1"); AddAttribute (node, "ok_total", "1"); AddAttribute (node, "extra", "1"); AddAttribute (node, "extra_total", "1"); } public void AddCountersAttributes (XmlNode node) { if (counters.Missing > 0) AddAttribute (node, "missing", counters.Missing.ToString ()); if (counters.Present > 0) AddAttribute (node, "present", counters.Present.ToString ()); if (counters.Extra > 0) AddAttribute (node, "extra", counters.Extra.ToString ()); if (counters.Ok > 0) AddAttribute (node, "ok", counters.Ok.ToString ()); if (counters.Total > 0) { int percent = (100 * counters.Ok / counters.Total); AddAttribute (node, "complete", percent.ToString ()); } if (counters.Todo > 0) AddAttribute (node, "todo", counters.Todo.ToString ()); if (counters.Warning > 0) AddAttribute (node, "warning", counters.Warning.ToString ()); if (counters.MissingTotal > 0) AddAttribute (node, "missing_total", counters.MissingTotal.ToString ()); if (counters.PresentTotal > 0) AddAttribute (node, "present_total", counters.PresentTotal.ToString ()); if (counters.ExtraTotal > 0) AddAttribute (node, "extra_total", counters.ExtraTotal.ToString ()); if (counters.OkTotal > 0) AddAttribute (node, "ok_total", counters.OkTotal.ToString ()); if (counters.AbsTotal > 0) { int percent = (100 * counters.OkTotal / counters.AbsTotal); AddAttribute (node, "complete_total", percent.ToString ()); } if (counters.TodoTotal > 0) { AddAttribute (node, "todo_total", counters.TodoTotal.ToString ()); //TODO: should be different on error. check error cases in corcompare. AddAttribute (node, "error_total", counters.Todo.ToString ()); } if (counters.WarningTotal > 0) AddAttribute (node, "warning_total", counters.WarningTotal.ToString ()); } protected void AddWarning (XmlNode parent, string fmt, params object [] args) { counters.Warning++; haveWarnings = true; XmlNode warnings = parent.SelectSingleNode ("warnings"); if (warnings == null) { warnings = document.CreateElement ("warnings", null); parent.AppendChild (warnings); } AddAttribute (parent, "error", "warning"); XmlNode warning = document.CreateElement ("warning", null); AddAttribute (warning, "text", String.Format (fmt, args)); warnings.AppendChild (warning); } public bool HaveWarnings { get { return haveWarnings; } } public Counters Counters { get { return counters; } } public abstract void CompareTo (XmlDocument doc, XmlNode parent, object other); } abstract class XMLNameGroup : XMLData { protected XmlNode group; protected Hashtable keys; public override void LoadData (XmlNode node) { if (node == null) throw new ArgumentNullException ("node"); if (node.Name != GroupName) throw new FormatException (String.Format ("Expecting <{0}>", GroupName)); keys = new Hashtable (); foreach (XmlNode n in node.ChildNodes) { string name = n.Attributes ["name"].Value; if (CheckIfAdd (name, n)) { string key = GetNodeKey (name, n); //keys.Add (key, name); keys [key] = name; LoadExtraData (key, n); } } } protected virtual bool CheckIfAdd (string value, XmlNode node) { return true; } protected virtual void LoadExtraData (string name, XmlNode node) { } public override void CompareTo (XmlDocument doc, XmlNode parent, object other) { this.document = doc; if (group == null) group = doc.CreateElement (GroupName, null); Hashtable okeys = null; if (other != null && ((XMLNameGroup) other).keys != null) { okeys = ((XMLNameGroup) other).keys; } XmlNode node = null; bool onull = (okeys == null); if (keys != null) { foreach (DictionaryEntry entry in keys) { node = doc.CreateElement (Name, null); group.AppendChild (node); string key = (string) entry.Key; string name = (string) entry.Value; AddAttribute (node, "name", name); if (!onull && HasKey (key, okeys)) { CompareToInner (key, node, (XMLNameGroup) other); okeys.Remove (key); counters.Present++; } else { AddAttribute (node, "presence", "missing"); counters.Missing++; } } } if (!onull && okeys.Count != 0) { foreach (string value in okeys.Values) { node = doc.CreateElement (Name, null); AddAttribute (node, "name", (string) value); AddAttribute (node, "presence", "extra"); group.AppendChild (node); counters.Extra++; } } if (group.HasChildNodes) parent.AppendChild (group); } protected virtual void CompareToInner (string name, XmlNode node, XMLNameGroup other) { } public virtual string GetNodeKey (string name, XmlNode node) { return name; } public virtual bool HasKey (string key, Hashtable other) { return other.ContainsKey (key); } public abstract string GroupName { get; } public abstract string Name { get; } } class XMLAssembly : XMLData { XMLAttributes attributes; XMLNamespace [] namespaces; string name; string version; public override void LoadData (XmlNode node) { if (node == null) throw new ArgumentNullException ("node"); name = node.Attributes ["name"].Value; version = node.Attributes ["version"].Value; XmlNode atts = node.FirstChild; attributes = new XMLAttributes (); if (atts.Name == "attributes") { attributes.LoadData (atts); atts = atts.NextSibling; } if (atts == null || atts.Name != "namespaces") { Console.Error.WriteLine ("Warning: no namespaces found!"); return; } namespaces = (XMLNamespace []) LoadRecursive (atts.ChildNodes, typeof (XMLNamespace)); } public override void CompareTo (XmlDocument doc, XmlNode parent, object other) { XMLAssembly assembly = (XMLAssembly) other; XmlNode childA = doc.CreateElement ("assembly", null); AddAttribute (childA, "name", name); AddAttribute (childA, "version", version); if (name != assembly.name) AddWarning (childA, "Assembly names not equal: {0}, {1}", name, assembly.name); if (version != assembly.version) AddWarning (childA, "Assembly version not equal: {0}, {1}", version, assembly.version); parent.AppendChild (childA); attributes.CompareTo (doc, childA, assembly.attributes); counters.AddPartialToPartial (attributes.Counters); CompareNamespaces (childA, assembly.namespaces); if (assembly.attributes != null && assembly.attributes.IsTodo) { counters.Todo++; counters.TodoTotal++; counters.ErrorTotal++; AddAttribute (childA, "error", "todo"); if (assembly.attributes.Comment != null) AddAttribute (childA, "comment", assembly.attributes.Comment); } AddCountersAttributes (childA); } void CompareNamespaces (XmlNode parent, XMLNamespace [] other) { ArrayList newNS = new ArrayList (); XmlNode group = document.CreateElement ("namespaces", null); parent.AppendChild (group); Hashtable oh = CreateHash (other); XmlNode node = null; int count = (namespaces == null) ? 0 : namespaces.Length; for (int i = 0; i < count; i++) { XMLNamespace xns = namespaces [i]; node = document.CreateElement ("namespace", null); newNS.Add (node); AddAttribute (node, "name", xns.Name); if (oh.ContainsKey (xns.Name)) { int idx = (int) oh [xns.Name]; xns.CompareTo (document, node, other [idx]); other [idx] = null; xns.AddCountersAttributes (node); counters.Present++; counters.PresentTotal++; counters.AddPartialToTotal (xns.Counters); } else { AddAttribute (node, "presence", "missing"); counters.Missing++; counters.MissingTotal++; } } if (other != null) { count = other.Length; for (int i = 0; i < count; i++) { XMLNamespace n = other [i]; if (n == null) continue; node = document.CreateElement ("namespace", null); newNS.Add (node); AddAttribute (node, "name", n.Name); AddExtra (node); counters.ExtraTotal++; } } XmlNode [] nodes = (XmlNode []) newNS.ToArray (typeof (XmlNode)); Array.Sort (nodes, XmlNodeComparer.Default); foreach (XmlNode nn in nodes) group.AppendChild (nn); } static Hashtable CreateHash (XMLNamespace [] other) { Hashtable result = new Hashtable (); if (other != null) { int i = 0; foreach (XMLNamespace n in other) { result [n.Name] = i++; } } return result; } public XmlDocument CompareAndGetDocument (XMLAssembly other) { XmlDocument doc = new XmlDocument (); this.document = doc; XmlNode parent = doc.CreateElement ("assemblies", null); doc.AppendChild (parent); CompareTo (doc, parent, other); XmlNode decl = doc.CreateXmlDeclaration ("1.0", null, null); doc.InsertBefore (decl, doc.DocumentElement); return doc; } } class XMLNamespace : XMLData { string name; XMLClass [] types; public override void LoadData (XmlNode node) { if (node == null) throw new ArgumentNullException ("node"); if (node.Name != "namespace") throw new FormatException ("Expecting "); name = node.Attributes ["name"].Value; XmlNode classes = node.FirstChild; if (classes == null) { Console.Error.WriteLine ("Warning: no classes for {0}", node.Attributes ["name"]); return; } if (classes.Name != "classes") throw new FormatException ("Expecting . Got <" + classes.Name + ">"); types = (XMLClass []) LoadRecursive (classes.ChildNodes, typeof (XMLClass)); } public override void CompareTo (XmlDocument doc, XmlNode parent, object other) { this.document = doc; XMLNamespace nspace = (XMLNamespace) other; XmlNode childA = doc.CreateElement ("classes", null); parent.AppendChild (childA); CompareTypes (childA, nspace.types); } void CompareTypes (XmlNode parent, XMLClass [] other) { ArrayList newNodes = new ArrayList (); Hashtable oh = CreateHash (other); XmlNode node = null; int count = (types == null) ? 0 : types.Length; for (int i = 0; i < count; i++) { XMLClass xclass = types [i]; node = document.CreateElement ("class", null); newNodes.Add (node); AddAttribute (node, "name", xclass.Name); AddAttribute (node, "type", xclass.Type); if (oh.ContainsKey (xclass.Name)) { int idx = (int) oh [xclass.Name]; xclass.CompareTo (document, node, other [idx]); other [idx] = null; counters.AddPartialToPartial (xclass.Counters); } else { AddAttribute (node, "presence", "missing"); counters.Missing++; counters.MissingTotal++; } } if (other != null) { count = other.Length; for (int i = 0; i < count; i++) { XMLClass c = other [i]; if (c == null || c.Name.EndsWith ("TODOAttribute")) continue; node = document.CreateElement ("class", null); newNodes.Add (node); AddAttribute (node, "name", c.Name); AddAttribute (node, "type", c.Type); AddExtra (node); counters.Extra++; counters.ExtraTotal++; } } XmlNode [] nodes = (XmlNode []) newNodes.ToArray (typeof (XmlNode)); Array.Sort (nodes, XmlNodeComparer.Default); foreach (XmlNode nn in nodes) parent.AppendChild (nn); } static Hashtable CreateHash (XMLClass [] other) { Hashtable result = new Hashtable (); if (other != null) { int i = 0; foreach (XMLClass c in other) { result [c.Name] = i++; } } return result; } public string Name { get { return name; } } } class XMLClass : XMLData { string name; string type; string baseName; bool isSealed; bool isSerializable; bool isAbstract; string charSet; string layout; XMLAttributes attributes; XMLInterfaces interfaces; XMLGenericTypeConstraints genericConstraints; XMLFields fields; XMLConstructors constructors; XMLProperties properties; XMLEvents events; XMLMethods methods; XMLClass [] nested; public override void LoadData (XmlNode node) { if (node == null) throw new ArgumentNullException ("node"); name = node.Attributes ["name"].Value; type = node.Attributes ["type"].Value; XmlAttribute xatt = node.Attributes ["base"]; if (xatt != null) baseName = xatt.Value; xatt = node.Attributes ["sealed"]; isSealed = (xatt != null && xatt.Value == "true"); xatt = node.Attributes ["abstract"]; isAbstract = (xatt != null && xatt.Value == "true"); xatt = node.Attributes["serializable"]; isSerializable = (xatt != null && xatt.Value == "true"); xatt = node.Attributes["charset"]; if (xatt != null) charSet = xatt.Value; xatt = node.Attributes["layout"]; if (xatt != null) layout = xatt.Value; XmlNode child = node.FirstChild; if (child == null) { // Console.Error.WriteLine ("Empty class {0} {1}", name, type); return; } if (child.Name == "attributes") { attributes = new XMLAttributes (); attributes.LoadData (child); child = child.NextSibling; } if (child != null && child.Name == "interfaces") { interfaces = new XMLInterfaces (); interfaces.LoadData (child); child = child.NextSibling; } if (child != null && child.Name == "generic-type-constraints") { genericConstraints = new XMLGenericTypeConstraints (); genericConstraints.LoadData (child); child = child.NextSibling; } if (child != null && child.Name == "fields") { fields = new XMLFields (); fields.LoadData (child); child = child.NextSibling; } if (child != null && child.Name == "constructors") { constructors = new XMLConstructors (); constructors.LoadData (child); child = child.NextSibling; } if (child != null && child.Name == "properties") { properties = new XMLProperties (); properties.LoadData (child); child = child.NextSibling; } if (child != null && child.Name == "events") { events = new XMLEvents (); events.LoadData (child); child = child.NextSibling; } if (child != null && child.Name == "methods") { methods = new XMLMethods (); methods.LoadData (child); child = child.NextSibling; } if (child == null) return; if (child.Name != "classes") { Console.WriteLine ("name: {0} type: {1} {2}", name, type, child.NodeType); throw new FormatException ("Expecting . Got <" + child.Name + ">"); } nested = (XMLClass []) LoadRecursive (child.ChildNodes, typeof (XMLClass)); } public override void CompareTo (XmlDocument doc, XmlNode parent, object other) { this.document = doc; XMLClass oclass = (XMLClass) other; if (attributes != null || oclass.attributes != null) { if (attributes == null) attributes = new XMLAttributes (); attributes.CompareTo (doc, parent, oclass.attributes); counters.AddPartialToPartial (attributes.Counters); if (oclass.attributes != null && oclass.attributes.IsTodo) { counters.Todo++; counters.TodoTotal++; counters.ErrorTotal++; AddAttribute (parent, "error", "todo"); if (oclass.attributes.Comment != null) AddAttribute (parent, "comment", oclass.attributes.Comment); } } if (type != oclass.type) AddWarning (parent, "Class type is wrong: {0} != {1}", type, oclass.type); if (baseName != oclass.baseName) AddWarning (parent, "Base class is wrong: {0} != {1}", baseName, oclass.baseName); if (isAbstract != oclass.isAbstract || isSealed != oclass.isSealed) { if ((isAbstract && isSealed) || (oclass.isAbstract && oclass.isSealed)) AddWarning (parent, "Should {0}be static", (isAbstract && isSealed) ? "" : "not "); else if (isAbstract != oclass.isAbstract) AddWarning (parent, "Should {0}be abstract", isAbstract ? "" : "not "); else if (isSealed != oclass.isSealed) AddWarning (parent, "Should {0}be sealed", isSealed ? "" : "not "); } if (isSerializable != oclass.isSerializable) AddWarning (parent, "Should {0}be serializable", isSerializable ? "" : "not "); if (charSet != oclass.charSet) AddWarning (parent, "CharSet is wrong: {0} != {1}", charSet, oclass.charSet); if (layout != oclass.layout) AddWarning (parent, "Layout is wrong: {0} != {1}", layout, oclass.layout); if (interfaces != null || oclass.interfaces != null) { if (interfaces == null) interfaces = new XMLInterfaces (); interfaces.CompareTo (doc, parent, oclass.interfaces); counters.AddPartialToPartial (interfaces.Counters); } if (genericConstraints != null || oclass.genericConstraints != null) { if (genericConstraints == null) genericConstraints = new XMLGenericTypeConstraints (); genericConstraints.CompareTo (doc, parent, oclass.genericConstraints); counters.AddPartialToPartial (genericConstraints.Counters); } if (fields != null || oclass.fields != null) { if (fields == null) fields = new XMLFields (); fields.CompareTo (doc, parent, oclass.fields); counters.AddPartialToPartial (fields.Counters); } if (constructors != null || oclass.constructors != null) { if (constructors == null) constructors = new XMLConstructors (); constructors.CompareTo (doc, parent, oclass.constructors); counters.AddPartialToPartial (constructors.Counters); } if (properties != null || oclass.properties != null) { if (properties == null) properties = new XMLProperties (); properties.CompareTo (doc, parent, oclass.properties); counters.AddPartialToPartial (properties.Counters); } if (events != null || oclass.events != null) { if (events == null) events = new XMLEvents (); events.CompareTo (doc, parent, oclass.events); counters.AddPartialToPartial (events.Counters); } if (methods != null || oclass.methods != null) { if (methods == null) methods = new XMLMethods (); methods.CompareTo (doc, parent, oclass.methods); counters.AddPartialToPartial (methods.Counters); } if (nested != null || oclass.nested != null) { XmlNode n = doc.CreateElement ("classes", null); parent.AppendChild (n); CompareTypes (n, oclass.nested); } AddCountersAttributes (parent); } void CompareTypes (XmlNode parent, XMLClass [] other) { ArrayList newNodes = new ArrayList (); Hashtable oh = CreateHash (other); XmlNode node = null; int count = (nested == null) ? 0 : nested.Length; for (int i = 0; i < count; i++) { XMLClass xclass = nested [i]; node = document.CreateElement ("nestedclass", null); newNodes.Add (node); AddAttribute (node, "name", xclass.Name); AddAttribute (node, "type", xclass.Type); if (oh.ContainsKey (xclass.Name)) { int idx = (int) oh [xclass.Name]; xclass.CompareTo (document, node, other [idx]); other [idx] = null; counters.AddPartialToPartial (xclass.Counters); } else { // TODO: Should I count here? AddAttribute (node, "presence", "missing"); counters.Missing++; counters.MissingTotal++; } } if (other != null) { count = other.Length; for (int i = 0; i < count; i++) { XMLClass c = other [i]; if (c == null || c.Name.EndsWith ("TODOAttribute")) continue; node = document.CreateElement ("nestedclass", null); newNodes.Add (node); AddAttribute (node, "name", c.Name); AddExtra (node); counters.Extra++; counters.ExtraTotal++; } } XmlNode [] nodes = (XmlNode []) newNodes.ToArray (typeof (XmlNode)); Array.Sort (nodes, XmlNodeComparer.Default); foreach (XmlNode nn in nodes) parent.AppendChild (nn); } static Hashtable CreateHash (XMLClass [] other) { Hashtable result = new Hashtable (); if (other != null) { int i = 0; foreach (XMLClass c in other) { result [c.Name] = i++; } } return result; } public string Name { get { return name; } } public string Type { get { return type; } } } class XMLParameter : XMLData { string name; string type; string attrib; string direction; bool isUnsafe; bool isOptional; string defaultValue; public override void LoadData (XmlNode node) { if (node == null) throw new ArgumentNullException ("node"); if (node.Name != "parameter") throw new ArgumentException ("Expecting "); name = node.Attributes["name"].Value; type = node.Attributes["type"].Value; attrib = node.Attributes["attrib"].Value; if (node.Attributes ["direction"] != null) direction = node.Attributes["direction"].Value; if (node.Attributes["unsafe"] != null) isUnsafe = bool.Parse (node.Attributes["unsafe"].Value); if (node.Attributes["optional"] != null) isOptional = bool.Parse (node.Attributes["optional"].Value); if (node.Attributes["defaultValue"] != null) defaultValue = node.Attributes["defaultValue"].Value; } public override void CompareTo (XmlDocument doc, XmlNode parent, object other) { this.document = doc; XMLParameter oparm = (XMLParameter) other; if (type != oparm.type) AddWarning (parent, "Parameter type is wrong: {0} != {1}", type, oparm.type); if (attrib != oparm.attrib) AddWarning (parent, "Parameter attributes wrong: {0} != {1}", attrib, oparm.attrib); if (direction != oparm.direction) AddWarning (parent, "Parameter direction wrong: {0} != {1}", direction, oparm.direction); if (isUnsafe != oparm.isUnsafe) AddWarning (parent, "Parameter unsafe wrong: {0} != {1}", isUnsafe, oparm.isUnsafe); if (isOptional != oparm.isOptional) AddWarning (parent, "Parameter optional wrong: {0} != {1}", isOptional, oparm.isOptional); if (defaultValue != oparm.defaultValue) AddWarning (parent, "Parameter default value wrong: {0} != {1}", (defaultValue == null) ? "(no default value)" : defaultValue, (oparm.defaultValue == null) ? "(no default value)" : oparm.defaultValue); } public string Name { get { return name; } } } class XMLAttributeProperties: XMLNameGroup { static Hashtable ignored_properties; static XMLAttributeProperties () { ignored_properties = new Hashtable (); ignored_properties.Add ("System.Reflection.AssemblyKeyFileAttribute", "KeyFile"); ignored_properties.Add ("System.Reflection.AssemblyCompanyAttribute", "Company"); ignored_properties.Add ("System.Reflection.AssemblyConfigurationAttribute", "Configuration"); ignored_properties.Add ("System.Reflection.AssemblyCopyrightAttribute", "Copyright"); ignored_properties.Add ("System.Reflection.AssemblyProductAttribute", "Product"); ignored_properties.Add ("System.Reflection.AssemblyTrademarkAttribute", "Trademark"); ignored_properties.Add ("System.Reflection.AssemblyInformationalVersionAttribute", "InformationalVersion"); ignored_properties.Add ("System.ObsoleteAttribute", "Message"); ignored_properties.Add ("System.IO.IODescriptionAttribute", "Description"); ignored_properties.Add ("System.Diagnostics.MonitoringDescriptionAttribute", "Description"); } Hashtable properties = new Hashtable (); string attribute; public XMLAttributeProperties (string attribute) { this.attribute = attribute; } public override void LoadData(XmlNode node) { if (node == null) throw new ArgumentNullException ("node"); if (node.ChildNodes == null) return; string ignored = ignored_properties [attribute] as string; foreach (XmlNode n in node.ChildNodes) { string name = n.Attributes ["name"].Value; if (ignored == name) continue; if (n.Attributes ["null"] != null) { properties.Add (name, null); continue; } string value = n.Attributes ["value"].Value; properties.Add (name, value); } } public override void CompareTo (XmlDocument doc, XmlNode parent, object other) { this.document = doc; Hashtable other_properties = ((XMLAttributeProperties)other).properties; foreach (DictionaryEntry de in other_properties) { object other_value = properties [de.Key]; if (de.Value == null) { if (other_value != null) AddWarning (parent, "Property '{0}' is 'null' and should be '{1}'", de.Key, other_value); continue; } if (de.Value.Equals (other_value)) continue; AddWarning (parent, "Property '{0}' is '{1}' and should be '{2}'", de.Key, de.Value, other_value == null ? "null" : other_value); } } public override string GroupName { get { return "properties"; } } public override string Name { get { return ""; } } } class XMLAttributes : XMLNameGroup { Hashtable properties = new Hashtable (); bool isTodo; string comment; protected override bool CheckIfAdd (string value, XmlNode node) { if (value.EndsWith ("TODOAttribute")) { isTodo = true; XmlNode pNode = node.SelectSingleNode ("properties"); if (pNode.ChildNodes [0].Attributes ["value"] != null) { comment = pNode.ChildNodes [0].Attributes ["value"].Value; } return false; } return true; } protected override void CompareToInner (string name, XmlNode node, XMLNameGroup other) { XMLAttributeProperties other_prop = ((XMLAttributes)other).properties [name] as XMLAttributeProperties; XMLAttributeProperties this_prop = properties [name] as XMLAttributeProperties; if (other_prop == null || this_prop == null) return; this_prop.CompareTo (document, node, other_prop); counters.AddPartialToPartial (this_prop.Counters); } public override string GetNodeKey (string name, XmlNode node) { string key = null; // if multiple attributes with the same name (type) exist, then we // cannot be sure which attributes correspond, so we must use the // name of the attribute (type) and the name/value of its properties // as key XmlNodeList attributes = node.ParentNode.SelectNodes("attribute[@name='" + name + "']"); if (attributes.Count > 1) { ArrayList keyParts = new ArrayList (); XmlNodeList properties = node.SelectNodes ("properties/property"); foreach (XmlNode property in properties) { XmlAttributeCollection attrs = property.Attributes; if (attrs["value"] != null) { keyParts.Add (attrs["name"].Value + "=" + attrs["value"].Value); } else { keyParts.Add (attrs["name"].Value + "="); } } // sort properties by name, as order of properties in XML is // undefined keyParts.Sort (); // insert name (type) of attribute keyParts.Insert (0, name); StringBuilder sb = new StringBuilder (); foreach (string value in keyParts) { sb.Append (value); sb.Append (';'); } key = sb.ToString (); } else { key = name; } return key; } protected override void LoadExtraData(string name, XmlNode node) { XmlNode pNode = node.SelectSingleNode ("properties"); if (name.EndsWith ("TODOAttribute")) { isTodo = true; if (pNode.ChildNodes [0].Attributes ["value"] != null) { comment = pNode.ChildNodes [0].Attributes ["value"].Value; } return; } if (pNode != null) { XMLAttributeProperties p = new XMLAttributeProperties (name); p.LoadData (pNode); properties[name] = p; } } public override string GroupName { get { return "attributes"; } } public override string Name { get { return "attribute"; } } public bool IsTodo { get { return isTodo; } } public string Comment { get { return comment; } } } class XMLInterfaces : XMLNameGroup { public override string GroupName { get { return "interfaces"; } } public override string Name { get { return "interface"; } } } abstract class XMLGenericGroup : XMLNameGroup { string attributes; protected override void LoadExtraData (string name, XmlNode node) { attributes = ((XmlElement) node).GetAttribute ("generic-attribute"); } protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other) { base.CompareToInner (name, parent, other); XMLGenericGroup g = (XMLGenericGroup) other; if (attributes != g.attributes) AddWarning (parent, "Incorrect generic attributes: '{0}' != '{1}'", attributes, g.attributes); } } class XMLGenericTypeConstraints : XMLGenericGroup { public override string GroupName { get { return "generic-type-constraints"; } } public override string Name { get { return "generic-type-constraint"; } } } class XMLGenericMethodConstraints : XMLGenericGroup { public override string GroupName { get { return "generic-method-constraints"; } } public override string Name { get { return "generic-method-constraint"; } } } abstract class XMLMember : XMLNameGroup { Hashtable attributeMap; Hashtable access = new Hashtable (); protected override void LoadExtraData (string name, XmlNode node) { XmlAttribute xatt = node.Attributes ["attrib"]; if (xatt != null) access [name] = xatt.Value; XmlNode orig = node; node = node.FirstChild; while (node != null) { if (node != null && node.Name == "attributes") { XMLAttributes a = new XMLAttributes (); a.LoadData (node); if (attributeMap == null) attributeMap = new Hashtable (); attributeMap [name] = a; break; } node = node.NextSibling; } base.LoadExtraData (name, orig); } protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other) { base.CompareToInner (name, parent, other); XMLMember mb = other as XMLMember; XMLAttributes att = null; XMLAttributes oatt = null; if (attributeMap != null) att = attributeMap [name] as XMLAttributes; if (mb != null && mb.attributeMap != null) oatt = mb.attributeMap [name] as XMLAttributes; if (att != null || oatt != null) { if (att == null) att = new XMLAttributes (); att.CompareTo (document, parent, oatt); counters.AddPartialToPartial(att.Counters); if (oatt != null && oatt.IsTodo) { counters.Todo++; counters.ErrorTotal++; AddAttribute (parent, "error", "todo"); if (oatt.Comment != null) AddAttribute (parent, "comment", oatt.Comment); } } XMLMember member = (XMLMember) other; string acc = access [name] as string; if (acc == null) return; string oacc = null; if (member.access != null) oacc = member.access [name] as string; string accName = ConvertToString (Int32.Parse (acc)); string oaccName = ""; if (oacc != null) oaccName = ConvertToString (Int32.Parse (oacc)); if (accName != oaccName) AddWarning (parent, "Incorrect attributes: '{0}' != '{1}'", accName, oaccName); } protected virtual string ConvertToString (int att) { return null; } } class XMLFields : XMLMember { Hashtable fieldTypes; Hashtable fieldValues; protected override void LoadExtraData (string name, XmlNode node) { XmlAttribute xatt = node.Attributes ["fieldtype"]; if (xatt != null) { if (fieldTypes == null) fieldTypes = new Hashtable (); fieldTypes [name] = xatt.Value; } xatt = node.Attributes ["value"]; if (xatt != null) { if (fieldValues == null) fieldValues = new Hashtable (); fieldValues[name] = xatt.Value; } base.LoadExtraData (name, node); } protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other) { base.CompareToInner (name, parent, other); XMLFields fields = (XMLFields) other; if (fieldTypes != null) { string ftype = fieldTypes [name] as string; string oftype = null; if (fields.fieldTypes != null) oftype = fields.fieldTypes [name] as string; if (ftype != oftype) AddWarning (parent, "Field type is {0} and should be {1}", oftype, ftype); } if (fieldValues != null) { string fvalue = fieldValues [name] as string; string ofvalue = null; if (fields.fieldValues != null) ofvalue = fields.fieldValues [name] as string; if (fvalue != ofvalue) AddWarning (parent, "Field value is {0} and should be {1}", ofvalue, fvalue); } } protected override string ConvertToString (int att) { FieldAttributes fa = (FieldAttributes) att; return fa.ToString (); } public override string GroupName { get { return "fields"; } } public override string Name { get { return "field"; } } } class XMLParameters : XMLNameGroup { public override void LoadData (XmlNode node) { if (node == null) throw new ArgumentNullException ("node"); if (node.Name != GroupName) throw new FormatException (String.Format ("Expecting <{0}>", GroupName)); keys = new Hashtable (); foreach (XmlNode n in node.ChildNodes) { string name = n.Attributes["name"].Value; string key = GetNodeKey (name, n); XMLParameter parm = new XMLParameter (); parm.LoadData (n); keys.Add (key, parm); LoadExtraData (key, n); } } public override string GroupName { get { return "parameters"; } } public override string Name { get { return "parameter"; } } public override string GetNodeKey (string name, XmlNode node) { return node.Attributes["position"].Value; } public override void CompareTo (XmlDocument doc, XmlNode parent, object other) { this.document = doc; if (group == null) group = doc.CreateElement (GroupName, null); Hashtable okeys = null; if (other != null && ((XMLParameters) other).keys != null) { okeys = ((XMLParameters) other).keys; } XmlNode node = null; bool onull = (okeys == null); if (keys != null) { foreach (DictionaryEntry entry in keys) { node = doc.CreateElement (Name, null); group.AppendChild (node); string key = (string) entry.Key; XMLParameter parm = (XMLParameter) entry.Value; AddAttribute (node, "name", parm.Name); if (!onull && HasKey (key, okeys)) { parm.CompareTo (document, node, okeys[key]); counters.AddPartialToPartial (parm.Counters); okeys.Remove (key); counters.Present++; } else { AddAttribute (node, "presence", "missing"); counters.Missing++; } } } if (!onull && okeys.Count != 0) { foreach (XMLParameter value in okeys.Values) { node = doc.CreateElement (Name, null); AddAttribute (node, "name", value.Name); AddAttribute (node, "presence", "extra"); group.AppendChild (node); counters.Extra++; } } if (group.HasChildNodes) parent.AppendChild (group); } } class XMLProperties : XMLMember { Hashtable nameToMethod = new Hashtable (); protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other) { Counters copy = counters; counters = new Counters(); XMLProperties oprop = other as XMLProperties; if (oprop != null) { XMLMethods m = nameToMethod [name] as XMLMethods; XMLMethods om = oprop.nameToMethod [name] as XMLMethods; if (m != null || om != null) { if (m == null) m = new XMLMethods (); m.CompareTo(document, parent, om); counters.AddPartialToPartial(m.Counters); } } base.CompareToInner (name, parent, other); AddCountersAttributes(parent); copy.AddPartialToPartial(counters); counters = copy; } protected override void LoadExtraData (string name, XmlNode node) { XmlNode orig = node; node = node.FirstChild; while (node != null) { if (node != null && node.Name == "methods") { XMLMethods m = new XMLMethods (); XmlNode parent = node.ParentNode; string key = GetNodeKey (name, parent); m.LoadData (node); nameToMethod [key] = m; break; } node = node.NextSibling; } base.LoadExtraData (name, orig); } public override string GetNodeKey (string name, XmlNode node) { XmlAttributeCollection atts = node.Attributes; return String.Format ("{0}:{1}:{2}", atts ["name"].Value, atts ["ptype"].Value, atts ["params"].Value); } public override string GroupName { get { return "properties"; } } public override string Name { get { return "property"; } } } class XMLEvents : XMLMember { Hashtable eventTypes; protected override void LoadExtraData (string name, XmlNode node) { XmlAttribute xatt = node.Attributes ["eventtype"]; if (xatt != null) { if (eventTypes == null) eventTypes = new Hashtable (); eventTypes [name] = xatt.Value; } base.LoadExtraData (name, node); } protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other) { Counters copy = counters; counters = new Counters (); try { base.CompareToInner (name, parent, other); AddCountersAttributes (parent); if (eventTypes == null) return; XMLEvents evt = (XMLEvents) other; string etype = eventTypes [name] as string; string oetype = null; if (evt.eventTypes != null) oetype = evt.eventTypes [name] as string; if (etype != oetype) AddWarning (parent, "Event type is {0} and should be {1}", oetype, etype); } finally { AddCountersAttributes (parent); copy.AddPartialToPartial (counters); counters = copy; } } protected override string ConvertToString (int att) { EventAttributes ea = (EventAttributes) att; return ea.ToString (); } public override string GroupName { get { return "events"; } } public override string Name { get { return "event"; } } } class XMLMethods : XMLMember { Hashtable returnTypes; Hashtable parameters; Hashtable genericConstraints; Hashtable signatureFlags; [Flags] enum SignatureFlags { None = 0, Abstract = 1, Virtual = 2, Static = 4 } protected override void LoadExtraData (string name, XmlNode node) { XmlAttribute xatt = node.Attributes ["returntype"]; if (xatt != null) { if (returnTypes == null) returnTypes = new Hashtable (); returnTypes [name] = xatt.Value; } SignatureFlags flags = SignatureFlags.None; if (((XmlElement) node).GetAttribute ("abstract") == "true") flags |= SignatureFlags.Abstract; if (((XmlElement) node).GetAttribute ("static") == "true") flags |= SignatureFlags.Static; if (((XmlElement) node).GetAttribute ("virtual") == "true") flags |= SignatureFlags.Virtual; if (flags != SignatureFlags.None) { if (signatureFlags == null) signatureFlags = new Hashtable (); signatureFlags [name] = flags; } XmlNode parametersNode = node.SelectSingleNode ("parameters"); if (parametersNode != null) { if (parameters == null) parameters = new Hashtable (); XMLParameters parms = new XMLParameters (); parms.LoadData (parametersNode); parameters[name] = parms; } XmlNode genericNode = node.SelectSingleNode ("generic-method-constraints"); if (genericNode != null) { if (genericConstraints == null) genericConstraints = new Hashtable (); XMLGenericMethodConstraints csts = new XMLGenericMethodConstraints (); csts.LoadData (genericNode); genericConstraints [name] = csts; } base.LoadExtraData (name, node); } protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other) { // create backup of actual counters Counters copy = counters; // initialize counters for current method counters = new Counters(); try { base.CompareToInner(name, parent, other); XMLMethods methods = (XMLMethods) other; SignatureFlags flags = signatureFlags != null && signatureFlags.ContainsKey (name) ? (SignatureFlags) signatureFlags [name] : SignatureFlags.None; SignatureFlags oflags = methods.signatureFlags != null && methods.signatureFlags.ContainsKey (name) ? (SignatureFlags) methods.signatureFlags [name] : SignatureFlags.None; if (flags!= oflags) { if (flags == SignatureFlags.None) AddWarning (parent, String.Format ("should not be {0}", oflags)); else if (oflags == SignatureFlags.None) AddWarning (parent, String.Format ("should be {0}", flags)); else AddWarning (parent, String.Format ("{0} and should be {1}", oflags, flags)); } if (returnTypes != null) { string rtype = returnTypes[name] as string; string ortype = null; if (methods.returnTypes != null) ortype = methods.returnTypes[name] as string; if (rtype != ortype) AddWarning (parent, "Return type is {0} and should be {1}", ortype, rtype); } if (parameters != null) { XMLParameters parms = parameters[name] as XMLParameters; parms.CompareTo (document, parent, methods.parameters[name]); counters.AddPartialToPartial (parms.Counters); } } finally { // output counter attributes in result document AddCountersAttributes(parent); // add temporary counters to actual counters copy.AddPartialToPartial(counters); // restore backup of actual counters counters = copy; } } protected override string ConvertToString (int att) { MethodAttributes ma = (MethodAttributes) att; // ignore ReservedMasks ma &= ~ MethodAttributes.ReservedMask; ma &= ~ MethodAttributes.VtableLayoutMask; if ((ma & MethodAttributes.FamORAssem) != 0) ma = (ma & ~ MethodAttributes.FamORAssem) | MethodAttributes.Family; // ignore the HasSecurity attribute for now if ((ma & MethodAttributes.HasSecurity) != 0) ma = (MethodAttributes) (att - (int) MethodAttributes.HasSecurity); // ignore the RequireSecObject attribute for now if ((ma & MethodAttributes.RequireSecObject) != 0) ma = (MethodAttributes) (att - (int) MethodAttributes.RequireSecObject); // we don't care if the implementation is forwarded through PInvoke if ((ma & MethodAttributes.PinvokeImpl) != 0) ma = (MethodAttributes) (att - (int) MethodAttributes.PinvokeImpl); return ma.ToString (); } public override string GroupName { get { return "methods"; } } public override string Name { get { return "method"; } } } class XMLConstructors : XMLMethods { public override string GroupName { get { return "constructors"; } } public override string Name { get { return "constructor"; } } } class XmlNodeComparer : IComparer { public static XmlNodeComparer Default = new XmlNodeComparer (); public int Compare (object a, object b) { XmlNode na = (XmlNode) a; XmlNode nb = (XmlNode) b; return String.Compare (na.Attributes ["name"].Value, nb.Attributes ["name"].Value); } } }