2003-11-14 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / tools / corcompare / mono-api-diff.cs
1 //
2 // mono-api-diff.cs - Compares 2 xml files produced by mono-api-info and
3 //                    produces a file suitable to build class status pages.
4 //
5 // Authors:
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (C) 2003 Novell, Inc (http://www.novell.com)
9 //
10
11 using System;
12 using System.Collections;
13 using System.IO;
14 using System.Reflection;
15 using System.Text;
16 using System.Xml;
17
18 namespace Mono.AssemblyCompare
19 {
20         class Driver
21         {
22                 static int Main (string [] args)
23                 {
24                         if (args.Length != 2)
25                                 return 1;
26
27                         XMLAssembly ms = CreateXMLAssembly (args [0]);
28                         XMLAssembly mono = CreateXMLAssembly (args [1]);
29                         XmlDocument doc = ms.CompareAndGetDocument (mono);
30
31                         XmlTextWriter writer = new XmlTextWriter (Console.Out);
32                         writer.Formatting = Formatting.Indented;
33                         doc.WriteTo (writer);
34
35                         return 0;
36                 }
37
38                 static XMLAssembly CreateXMLAssembly (string file)
39                 {
40                         XmlDocument doc = new XmlDocument ();
41                         doc.Load (File.OpenRead (file));
42
43                         XmlNode node = doc.SelectSingleNode ("/assemblies/assembly");
44                         XMLAssembly result = new XMLAssembly ();
45                         try {
46                                 result.LoadData (node);
47                         } catch (Exception e) {
48                                 Console.Error.WriteLine ("Error loading {0}: {1}\n{2}", file, e.Message, e);
49                                 Environment.Exit (1);
50                         }
51
52                         return result;
53                 }
54         }
55
56         class Counters
57         {
58                 public int Present;
59                 public int PresentTotal;
60                 public int Missing;
61                 public int MissingTotal;
62                 public int Todo;
63                 public int TodoTotal;
64
65                 public int Extra;
66                 public int ExtraTotal;
67                 public int Warning;
68                 public int WarningTotal;
69                 public int ErrorTotal;
70
71                 public Counters ()
72                 {
73                 }
74
75                 public void AddPartialToPartial (Counters other)
76                 {
77                         Present += other.Present;
78                         Extra += other.Extra;
79                         Missing += other.Missing;
80
81                         Todo += other.Todo;
82                         Warning += other.Warning;
83                         AddPartialToTotal (other);
84                 }
85
86                 public void AddPartialToTotal (Counters other)
87                 {
88                         PresentTotal += other.Present;
89                         ExtraTotal += other.Extra;
90                         MissingTotal += other.Missing;
91
92                         TodoTotal += other.Todo;
93                         WarningTotal += other.Warning;
94                 }
95
96                 public void AddTotalToPartial (Counters other)
97                 {
98                         Present += other.PresentTotal;
99                         Extra += other.ExtraTotal;
100                         Missing += other.MissingTotal;
101
102                         Todo += other.TodoTotal;
103                         Warning += other.WarningTotal;
104                         AddTotalToTotal (other);
105                 }
106
107                 public void AddTotalToTotal (Counters other)
108                 {
109                         PresentTotal += other.PresentTotal;
110                         ExtraTotal += other.ExtraTotal;
111                         MissingTotal += other.MissingTotal;
112
113                         TodoTotal += other.TodoTotal;
114                         WarningTotal += other.WarningTotal;
115                         ErrorTotal += other.ErrorTotal;
116                 }
117
118                 public int Total {
119                         get { return Present + Missing; }
120                 }
121
122                 public int AbsTotal {
123                         get { return PresentTotal + MissingTotal; }
124                 }
125
126                 public int Ok {
127                         get { return Present - Todo; }
128                 }
129
130                 public int OkTotal {
131                         get { return PresentTotal - TodoTotal - ErrorTotal; }
132                 }
133
134                 public override string ToString ()
135                 {
136                         StringWriter sw = new StringWriter ();
137                         sw.WriteLine ("Present: {0}", Present);
138                         sw.WriteLine ("PresentTotal: {0}", PresentTotal);
139                         sw.WriteLine ("Missing: {0}", Missing);
140                         sw.WriteLine ("MissingTotal: {0}", MissingTotal);
141                         sw.WriteLine ("Todo: {0}", Todo);
142                         sw.WriteLine ("TodoTotal: {0}", TodoTotal);
143                         sw.WriteLine ("Extra: {0}", Extra);
144                         sw.WriteLine ("ExtraTotal: {0}", ExtraTotal);
145                         sw.WriteLine ("Warning: {0}", Warning);
146                         sw.WriteLine ("WarningTotal: {0}", WarningTotal);
147                         sw.WriteLine ("ErrorTotal: {0}", ErrorTotal);
148                         sw.WriteLine ("--");
149                         return sw.GetStringBuilder ().ToString ();
150                 }
151         }
152
153         abstract class XMLData
154         {
155                 protected XmlDocument document;
156                 protected Counters counters;
157                 bool haveWarnings;
158
159                 public XMLData ()
160                 {
161                         counters = new Counters ();
162                 }
163
164                 public virtual void LoadData (XmlNode node)
165                 {
166                 }
167
168                 protected object [] LoadRecursive (XmlNodeList nodeList, Type type)
169                 {
170                         ArrayList list = new ArrayList ();
171                         foreach (XmlNode node in nodeList) {
172                                 XMLData data = (XMLData) Activator.CreateInstance (type);
173                                 data.LoadData (node);
174                                 list.Add (data);
175                         }
176
177                         return (object []) list.ToArray (type);
178                 }
179
180                 protected void AddAttribute (XmlNode node, string name, string value)
181                 {
182                         XmlAttribute attr = document.CreateAttribute (name);
183                         attr.Value = value;
184                         node.Attributes.Append (attr);
185                 }
186
187                 protected void AddExtra (XmlNode node)
188                 {
189                         //TODO: count all the subnodes?
190                         AddAttribute (node, "presence", "extra");
191                         AddAttribute (node, "ok", "1");
192                         AddAttribute (node, "ok_total", "1");
193                         AddAttribute (node, "extra", "1");
194                         AddAttribute (node, "extra_total", "1");
195                 }
196
197                 public void AddCountersAttributes (XmlNode node)
198                 {
199                         if (counters.Missing > 0)
200                                 AddAttribute (node, "missing", counters.Missing.ToString ());
201
202                         if (counters.Present > 0)
203                                 AddAttribute (node, "present", counters.Present.ToString ());
204
205                         if (counters.Extra > 0)
206                                 AddAttribute (node, "extra", counters.Extra.ToString ());
207
208                         if (counters.Ok > 0)
209                                 AddAttribute (node, "ok", counters.Ok.ToString ());
210
211                         if (counters.Total > 0) {
212                                 int percent = (100 * counters.Ok / counters.Total);
213                                 AddAttribute (node, "complete", percent.ToString ());
214                         }
215
216                         if (counters.Todo > 0)
217                                 AddAttribute (node, "todo", counters.Todo.ToString ());
218
219                         if (counters.Warning > 0)
220                                 AddAttribute (node, "warning", counters.Warning.ToString ());
221
222                         if (counters.MissingTotal > 0)
223                                 AddAttribute (node, "missing_total", counters.MissingTotal.ToString ());
224
225                         if (counters.PresentTotal > 0)
226                                 AddAttribute (node, "present_total", counters.PresentTotal.ToString ());
227
228                         if (counters.ExtraTotal > 0)
229                                 AddAttribute (node, "extra_total", counters.ExtraTotal.ToString ());
230
231                         if (counters.OkTotal > 0)
232                                 AddAttribute (node, "ok_total", counters.OkTotal.ToString ());
233
234                         if (counters.AbsTotal > 0) {
235                                 int percent = (100 * counters.OkTotal / counters.AbsTotal);
236                                 AddAttribute (node, "complete_total", percent.ToString ());
237                         }
238
239                         if (counters.TodoTotal > 0) {
240                                 AddAttribute (node, "todo_total", counters.TodoTotal.ToString ());
241                                 //TODO: should be different on error. check error cases in corcompare.
242                                 AddAttribute (node, "error_total", counters.Todo.ToString ());
243                         }
244
245                         if (counters.WarningTotal > 0)
246                                 AddAttribute (node, "warning_total", counters.WarningTotal.ToString ());
247
248                 }
249
250                 protected void AddWarning (XmlNode parent, string fmt, params object [] args)
251                 {
252                         counters.Warning++;
253                         haveWarnings = true;
254                         XmlNode warnings = parent.SelectSingleNode ("warnings");
255                         if (warnings == null) {
256                                 warnings = document.CreateElement ("warnings", null);
257                                 parent.AppendChild (warnings);
258                         }
259
260                         AddAttribute (parent, "error", "warning");
261                         XmlNode warning = document.CreateElement ("warnings", null);
262                         AddAttribute (warning, "text", String.Format (fmt, args));
263                         warnings.AppendChild (warning);
264                 }
265
266                 public bool HaveWarnings {
267                         get { return haveWarnings; }
268                 }
269                 
270                 public Counters Counters {
271                         get { return counters; }
272                 }
273                 
274                 public abstract void CompareTo (XmlDocument doc, XmlNode parent, object other);
275         }
276         
277         abstract class XMLNameGroup : XMLData
278         {
279                 protected XmlNode group;
280                 protected Hashtable keys;
281
282                 public override void LoadData (XmlNode node)
283                 {
284                         if (node == null)
285                                 throw new ArgumentNullException ("node");
286
287                         if (node.Name != GroupName)
288                                 throw new FormatException (String.Format ("Expecting <{0}>", GroupName));
289
290                         keys = new Hashtable ();
291                         foreach (XmlNode n in node.ChildNodes) {
292                                 string name = n.Attributes ["name"].Value;
293                                 if (CheckIfAdd (name)) {
294                                         string key = GetNodeKey (name, n);
295                                         keys.Add (key, name);
296                                         if (n.HasChildNodes)
297                                                 LoadExtraData (key, n.FirstChild);
298                                 }
299                         }
300                 }
301
302                 protected virtual bool CheckIfAdd (string value)
303                 {
304                         return true;
305                 }
306
307                 protected virtual void LoadExtraData (string name, XmlNode node)
308                 {
309                 }
310
311                 public override void CompareTo (XmlDocument doc, XmlNode parent, object other)
312                 {
313                         this.document = doc;
314                         if (group == null)
315                                 group = doc.CreateElement (GroupName, null);
316
317                         Hashtable okeys = null;
318                         if (other != null && ((XMLNameGroup) other).keys != null) {
319                                 okeys = ((XMLNameGroup) other).keys;
320                         }
321
322                         XmlNode node = null;
323                         bool onull = (okeys == null);
324                         if (keys != null) {
325                                 foreach (DictionaryEntry entry in keys) {
326                                         node = doc.CreateElement (Name, null);
327                                         group.AppendChild (node);
328                                         string key = (string) entry.Key;
329                                         string name = (string) entry.Value;
330                                         AddAttribute (node, "name", name);
331
332                                         if (!onull && HasKey (key, okeys)) {
333                                                 CompareToInner (key, node, (XMLNameGroup) other);
334                                                 okeys.Remove (key);
335                                                 counters.Present++;
336                                         } else {
337                                                 AddAttribute (node, "presence", "missing");
338                                                 counters.Missing++;
339                                         }
340                                 }
341                         }
342
343                         if (!onull && okeys.Count != 0) {
344                                 foreach (string value in okeys.Values) {
345                                         node = doc.CreateElement (Name, null);
346                                         AddAttribute (node, "name", (string) value);
347                                         AddAttribute (node, "presence", "extra");
348                                         group.AppendChild (node);
349                                         counters.Extra++;
350                                 }
351                         }
352
353                         if (group.HasChildNodes)
354                                 parent.AppendChild (group);
355                 }
356
357                 protected virtual void CompareToInner (string name, XmlNode node, XMLNameGroup other)
358                 {
359                 }
360
361                 public virtual string GetNodeKey (string name, XmlNode node)
362                 {
363                         return name;
364                 }
365
366                 public virtual bool HasKey (string key, Hashtable other)
367                 {
368                         return other.ContainsKey (key);
369                 }
370
371                 public abstract string GroupName { get; }
372                 public abstract string Name { get; }
373         }
374
375         class XMLAssembly : XMLData
376         {
377                 XMLAttributes attributes;
378                 XMLNamespace [] namespaces;
379                 string name;
380                 string version;
381
382                 public override void LoadData (XmlNode node)
383                 {
384                         if (node == null)
385                                 throw new ArgumentNullException ("node");
386
387                         name = node.Attributes ["name"].Value;
388                         version = node.Attributes  ["version"].Value;
389                         XmlNode atts = node.FirstChild;
390                         attributes = new XMLAttributes ();
391                         if (atts.Name == "attributes") {
392                                 attributes.LoadData (atts);
393                                 atts = atts.NextSibling;
394                         }
395
396                         if (atts == null || atts.Name != "namespaces") {
397                                 Console.Error.WriteLine ("Warning: no namespaces found!");
398                                 return;
399                         }
400
401                         namespaces = (XMLNamespace []) LoadRecursive (atts.ChildNodes, typeof (XMLNamespace));
402                 }
403
404                 public override void CompareTo (XmlDocument doc, XmlNode parent, object other)
405                 {
406                         XMLAssembly assembly = (XMLAssembly) other;
407
408                         XmlNode childA = doc.CreateElement ("assembly", null);
409                         AddAttribute (childA, "name", name);
410                         AddAttribute (childA, "version", version);
411                         if (name != assembly.name)
412                                 AddWarning (childA, "Assembly names not equal: {0}, {1}", name, assembly.name);
413
414                         if (version != assembly.version)
415                                 AddWarning (childA, "Assembly version not equal: {0}, {1}", version, assembly.version);
416
417                         parent.AppendChild (childA);
418
419                         attributes.CompareTo (doc, childA, assembly.attributes);
420                         counters.AddPartialToPartial (attributes.Counters);
421
422                         CompareNamespaces (childA, assembly.namespaces);
423                         if (assembly.attributes != null && assembly.attributes.IsTodo) {
424                                 counters.Todo++;
425                                 counters.TodoTotal++;
426                                 counters.ErrorTotal++;
427                                 AddAttribute (childA, "error", "todo");
428                         }
429
430                         AddCountersAttributes (childA);
431                 }
432
433                 void CompareNamespaces (XmlNode parent, XMLNamespace [] other)
434                 {
435                         ArrayList newNS = new ArrayList ();
436                         XmlNode group = document.CreateElement ("namespaces", null);
437                         parent.AppendChild (group);
438
439                         Hashtable oh = CreateHash (other);
440                         XmlNode node = null;
441                         int count = (namespaces == null) ? 0 : namespaces.Length;
442                         for (int i = 0; i < count; i++) {
443                                 XMLNamespace xns = namespaces [i];
444
445                                 node = document.CreateElement ("namespace", null);
446                                 newNS.Add (node);
447                                 AddAttribute (node, "name", xns.Name);
448
449                                 if (oh.ContainsKey (xns.Name)) {
450                                         int idx = (int) oh [xns.Name];
451                                         xns.CompareTo (document, node, other [idx]);
452                                         other [idx] = null;
453                                         xns.AddCountersAttributes (node);
454                                         counters.Present++;
455                                         counters.PresentTotal++;
456                                         counters.AddPartialToTotal (xns.Counters);
457                                 } else {
458                                         AddAttribute (node, "presence", "missing");
459                                         counters.Missing++;
460                                         counters.MissingTotal++;
461                                 }
462                         }
463
464                         if (other != null) {
465                                 count = other.Length;
466                                 for (int i = 0; i < count; i++) {
467                                         XMLNamespace n = other [i];
468                                         if (n == null)
469                                                 continue;
470
471                                         node = document.CreateElement ("namespace", null);
472                                         newNS.Add (node);
473                                         AddAttribute (node, "name", n.Name);
474                                         AddExtra (node);
475                                         counters.ExtraTotal++;
476                                 }
477                         }
478
479                         XmlNode [] nodes = (XmlNode []) newNS.ToArray (typeof (XmlNode));
480                         Array.Sort (nodes, XmlNodeComparer.Default);
481                         foreach (XmlNode nn in nodes)
482                                 group.AppendChild (nn);
483                 }
484
485                 static Hashtable CreateHash (XMLNamespace [] other)
486                 {
487                         Hashtable result = new Hashtable ();
488                         if (other != null) {
489                                 int i = 0;
490                                 foreach (XMLNamespace n in other) {
491                                         result [n.Name] = i++;
492                                 }
493                         }
494
495                         return result;
496                 }
497
498                 public XmlDocument CompareAndGetDocument (XMLAssembly other)
499                 {
500                         XmlDocument doc = new XmlDocument ();
501                         this.document = doc;
502                         XmlNode parent = doc.CreateElement ("assemblies", null);
503                         doc.AppendChild (parent);
504                         
505                         CompareTo (doc, parent, other);
506
507                         XmlNode decl = doc.CreateXmlDeclaration ("1.0", null, null);
508                         doc.InsertBefore (decl, doc.DocumentElement);
509
510                         return doc;
511                 }
512         }
513
514         class XMLNamespace : XMLData
515         {
516                 string name;
517                 XMLClass [] types;
518
519                 public override void LoadData (XmlNode node)
520                 {
521                         if (node == null)
522                                 throw new ArgumentNullException ("node");
523
524                         if (node.Name != "namespace")
525                                 throw new FormatException ("Expecting <namespace>");
526
527                         name = node.Attributes  ["name"].Value;
528                         XmlNode classes = node.FirstChild;
529                         if (classes == null) {
530                                 Console.Error.WriteLine ("Warning: no classes for {0}", node.Attributes  ["name"]);
531                                 return;
532                         }
533
534                         if (classes.Name != "classes")
535                                 throw new FormatException ("Expecting <classes>. Got <" + classes.Name + ">");
536
537                         types = (XMLClass []) LoadRecursive (classes.ChildNodes, typeof (XMLClass));
538                 }
539
540                 public override void CompareTo (XmlDocument doc, XmlNode parent, object other)
541                 {
542                         this.document = doc;
543                         XMLNamespace nspace = (XMLNamespace) other;
544
545                         XmlNode childA = doc.CreateElement ("classes", null);
546                         parent.AppendChild (childA);
547
548                         CompareTypes (childA, nspace.types);
549                 }
550
551                 void CompareTypes (XmlNode parent, XMLClass [] other)
552                 {
553                         ArrayList newNodes = new ArrayList ();
554                         Hashtable oh = CreateHash (other);
555                         XmlNode node = null;
556                         int count = (types == null) ? 0 : types.Length;
557                         for (int i = 0; i < count; i++) {
558                                 XMLClass xclass = types [i];
559
560                                 node = document.CreateElement ("class", null);
561                                 newNodes.Add (node);
562                                 AddAttribute (node, "name", xclass.Name);
563                                 AddAttribute (node, "type", xclass.Type);
564
565                                 if (oh.ContainsKey (xclass.Name)) {
566                                         int idx = (int) oh [xclass.Name];
567                                         xclass.CompareTo (document, node, other [idx]);
568                                         other [idx] = null;
569                                         counters.AddPartialToPartial (xclass.Counters);
570                                 } else {
571                                         AddAttribute (node, "presence", "missing");
572                                         counters.Missing++;
573                                         counters.MissingTotal++;
574                                 }
575                         }
576
577                         if (other != null) {
578                                 count = other.Length;
579                                 for (int i = 0; i < count; i++) {
580                                         XMLClass c = other [i];
581                                         if (c == null || c.Name == "MonoTODOAttribute")
582                                                 continue;
583
584                                         node = document.CreateElement ("class", null);
585                                         newNodes.Add (node);
586                                         AddAttribute (node, "name", c.Name);
587                                         AddAttribute (node, "type", c.Type);
588                                         AddExtra (node);
589                                         counters.Extra++;
590                                         counters.ExtraTotal++;
591                                 }
592                         }
593
594                         XmlNode [] nodes = (XmlNode []) newNodes.ToArray (typeof (XmlNode));
595                         Array.Sort (nodes, XmlNodeComparer.Default);
596                         foreach (XmlNode nn in nodes)
597                                 parent.AppendChild (nn);
598                 }
599
600                 static Hashtable CreateHash (XMLClass [] other)
601                 {
602                         Hashtable result = new Hashtable ();
603                         if (other != null) {
604                                 int i = 0;
605                                 foreach (XMLClass c in other) {
606                                         result [c.Name] = i++;
607                                 }
608                         }
609
610                         return result;
611                 }
612
613                 public string Name {
614                         get { return name; }
615                 }
616         }
617
618         class XMLClass : XMLData
619         {
620                 string name;
621                 string type;
622                 string baseName;
623                 bool isSealed;
624                 XMLAttributes attributes;
625                 XMLInterfaces interfaces;
626                 XMLFields fields;
627                 XMLConstructors constructors;
628                 XMLProperties properties;
629                 XMLEvents events;
630                 XMLMethods methods;
631                 XMLClass [] nested;
632                 
633                 public override void LoadData (XmlNode node)
634                 {
635                         if (node == null)
636                                 throw new ArgumentNullException ("node");
637
638                         name = node.Attributes ["name"].Value;
639                         type = node.Attributes  ["type"].Value;
640                         XmlAttribute xatt = node.Attributes ["base"];
641                         if (xatt != null)
642                                 baseName = xatt.Value;
643
644                         xatt = node.Attributes ["sealed"];
645                         isSealed = (xatt != null && xatt.Value == "true");
646
647                         XmlNode child = node.FirstChild;
648                         if (child == null) {
649                                 // Console.Error.WriteLine ("Empty class {0} {1}", name, type);
650                                 return;
651                         }
652                                 
653                         if (child.Name == "attributes") {
654                                 attributes = new XMLAttributes ();
655                                 attributes.LoadData (child);
656                                 child = child.NextSibling;
657                         }
658
659                         if (child != null && child.Name == "interfaces") {
660                                 interfaces = new XMLInterfaces ();
661                                 interfaces.LoadData (child);
662                                 child = child.NextSibling;
663                         }
664
665                         if (child != null && child.Name == "fields") {
666                                 fields = new XMLFields ();
667                                 fields.LoadData (child);
668                                 child = child.NextSibling;
669                         }
670
671                         if (child != null && child.Name == "constructors") {
672                                 constructors = new XMLConstructors ();
673                                 constructors.LoadData (child);
674                                 child = child.NextSibling;
675                         }
676
677                         if (child != null && child.Name == "properties") {
678                                 properties = new XMLProperties ();
679                                 properties.LoadData (child);
680                                 child = child.NextSibling;
681                         }
682
683                         if (child != null && child.Name == "events") {
684                                 events = new XMLEvents ();
685                                 events.LoadData (child);
686                                 child = child.NextSibling;
687                         }
688
689                         if (child != null && child.Name == "methods") {
690                                 methods = new XMLMethods ();
691                                 methods.LoadData (child);
692                                 child = child.NextSibling;
693                         }
694
695                         if (child == null)
696                                 return;
697
698                         if (child.Name != "classes") {
699                                 Console.WriteLine ("name: {0} type: {1} {2}", name, type, child.NodeType);
700                                 throw new FormatException ("Expecting <classes>. Got <" + child.Name + ">");
701                         }
702
703                         nested = (XMLClass []) LoadRecursive (child.ChildNodes, typeof (XMLClass));
704                 }
705
706                 public override void CompareTo (XmlDocument doc, XmlNode parent, object other)
707                 {
708                         this.document = doc;
709                         XMLClass oclass = (XMLClass) other;
710
711                         if (attributes != null || oclass.attributes != null) {
712                                 if (attributes == null)
713                                         attributes = new XMLAttributes ();
714
715                                 attributes.CompareTo (doc, parent, oclass.attributes);
716                                 counters.AddPartialToPartial (attributes.Counters);
717                                 if (oclass.attributes != null && oclass.attributes.IsTodo) {
718                                         counters.Todo++;
719                                         counters.TodoTotal++;
720                                         counters.ErrorTotal++;
721                                         AddAttribute (parent, "error", "todo");
722                                 }
723                         }
724
725                         if (type != oclass.type)
726                                 AddWarning (parent, "Class type is wrong: {0} != {1}", type, oclass.type);
727
728                         if (baseName != oclass.baseName)
729                                 AddWarning (parent, "Base class is wrong: {0} != {1}", baseName, oclass.baseName);
730
731                         if (isSealed != oclass.isSealed)
732                                 AddWarning (parent, "Should {0}be sealed", isSealed ? "" : "not ");
733
734                         if (interfaces != null || oclass.interfaces != null) {
735                                 if (interfaces == null)
736                                         interfaces = new XMLInterfaces ();
737
738                                 interfaces.CompareTo (doc, parent, oclass.interfaces);
739                                 counters.AddPartialToPartial (interfaces.Counters);
740                         }
741
742                         if (fields != null || oclass.fields != null) {
743                                 if (fields == null)
744                                         fields = new XMLFields ();
745
746                                 fields.CompareTo (doc, parent, oclass.fields);
747                                 counters.AddPartialToPartial (fields.Counters);
748                         }
749
750                         if (constructors != null || oclass.constructors != null) {
751                                 if (constructors == null)
752                                         constructors = new XMLConstructors ();
753
754                                 constructors.CompareTo (doc, parent, oclass.constructors);
755                                 counters.AddPartialToPartial (constructors.Counters);
756                         }
757
758                         if (properties != null || oclass.properties != null) {
759                                 if (properties == null)
760                                         properties = new XMLProperties ();
761
762                                 properties.CompareTo (doc, parent, oclass.properties);
763                                 counters.AddPartialToPartial (properties.Counters);
764                         }
765
766                         if (events != null || oclass.events != null) {
767                                 if (events == null)
768                                         events = new XMLEvents ();
769
770                                 events.CompareTo (doc, parent, oclass.events);
771                                 counters.AddPartialToPartial (events.Counters);
772                         }
773
774                         if (methods != null || oclass.methods != null) {
775                                 if (methods == null)
776                                         methods = new XMLMethods ();
777
778                                 methods.CompareTo (doc, parent, oclass.methods);
779                                 counters.AddPartialToPartial (methods.Counters);
780                         }
781
782                         if (nested != null || oclass.nested != null) {
783                                 XmlNode n = doc.CreateElement ("classes", null);
784                                 parent.AppendChild (n);
785                                 CompareTypes (n, oclass.nested);
786                         }
787
788                         AddCountersAttributes (parent);
789                 }
790
791                 void CompareTypes (XmlNode parent, XMLClass [] other)
792                 {
793                         ArrayList newNodes = new ArrayList ();
794                         Hashtable oh = CreateHash (other);
795                         XmlNode node = null;
796                         int count = (nested == null) ? 0 : nested.Length;
797                         for (int i = 0; i < count; i++) {
798                                 XMLClass xclass = nested [i];
799
800                                 node = document.CreateElement ("nestedclass", null);
801                                 newNodes.Add (node);
802                                 AddAttribute (node, "name", xclass.Name);
803                                 AddAttribute (node, "type", xclass.Type);
804
805                                 if (oh.ContainsKey (xclass.Name)) {
806                                         int idx = (int) oh [xclass.Name];
807                                         xclass.CompareTo (document, node, other [idx]);
808                                         other [idx] = null;
809                                         counters.AddPartialToPartial (xclass.Counters);
810                                 } else {
811                                         // TODO: Should I count here?
812                                         AddAttribute (node, "presence", "missing");
813                                         counters.Missing++;
814                                         counters.MissingTotal++;
815                                 }
816                         }
817
818                         if (other != null) {
819                                 count = other.Length;
820                                 for (int i = 0; i < count; i++) {
821                                         XMLClass c = other [i];
822                                         if (c == null || c.Name == "MonoTODOAttribute")
823                                                 continue;
824
825                                         node = document.CreateElement ("nestedclass", null);
826                                         newNodes.Add (node);
827                                         AddAttribute (node, "name", c.Name);
828                                         AddExtra (node);
829                                         counters.Extra++;
830                                         counters.ExtraTotal++;
831                                 }
832                         }
833
834                         XmlNode [] nodes = (XmlNode []) newNodes.ToArray (typeof (XmlNode));
835                         Array.Sort (nodes, XmlNodeComparer.Default);
836                         foreach (XmlNode nn in nodes)
837                                 parent.AppendChild (nn);
838                 }
839
840                 static Hashtable CreateHash (XMLClass [] other)
841                 {
842                         Hashtable result = new Hashtable ();
843                         if (other != null) {
844                                 int i = 0;
845                                 foreach (XMLClass c in other) {
846                                         result [c.Name] = i++;
847                                 }
848                         }
849
850                         return result;
851                 }
852
853                 public string Name {
854                         get { return name; }
855                 }
856
857                 public string Type {
858                         get { return type; }
859                 }
860         }
861
862         class XMLAttributes : XMLNameGroup
863         {
864                 bool isTodo;
865
866                 protected override bool CheckIfAdd (string value)
867                 {
868                         if (value.EndsWith (".MonoTODOAttribute")) {
869                                 isTodo = true;
870                                 return false;
871                         }
872
873                         return true;
874                 }
875
876                 public override string GetNodeKey (string name, XmlNode node)
877                 {
878                         int i = 0;
879                         while (keys.ContainsKey (name)) {
880                                 name = String.Format ("{0}:{1}", name, i++);
881                         }
882
883                         return name;
884                 }
885
886                 public override string GroupName {
887                         get { return "attributes"; }
888                 }
889
890                 public override string Name {
891                         get { return "attribute"; }
892                 }
893
894                 public bool IsTodo {
895                         get { return isTodo; }
896                 }
897         }
898
899         class XMLInterfaces : XMLNameGroup
900         {
901                 public override string GroupName {
902                         get { return "interfaces"; }
903                 }
904
905                 public override string Name {
906                         get { return "interface"; }
907                 }
908         }
909
910         abstract class XMLMember : XMLNameGroup
911         {
912                 Hashtable attributeMap;
913                 Hashtable access = new Hashtable ();
914
915                 protected override void LoadExtraData (string name, XmlNode node)
916                 {
917                         XmlAttribute xatt = node.Attributes ["attrib"];
918                         if (xatt != null)
919                                 access [name] = xatt.Value;
920                         
921                         XmlNode orig = node;
922                         while (node != null) {
923                                 if (node != null && node.Name == "attributes") {
924                                         XMLAttributes a = new XMLAttributes ();
925                                         a.LoadData (node);
926                                         if (attributeMap == null)
927                                                 attributeMap = new Hashtable ();
928
929                                         attributeMap [name] = a;
930                                         break;
931                                 }
932                                 node = node.NextSibling;
933                         }
934
935                         base.LoadExtraData (name, orig);
936                 }
937
938                 protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other)
939                 {
940                         base.CompareToInner (name, parent, other);
941                         XMLMember mb = other as XMLMember;
942                         XMLAttributes att = null;
943                         XMLAttributes oatt = null;
944                         if (attributeMap != null)
945                                 att = attributeMap [name] as XMLAttributes;
946
947                         if (mb != null && mb.attributeMap != null)
948                                 oatt = mb.attributeMap [name] as XMLAttributes;
949
950                         if (att != null || oatt != null) {
951                                 if (att == null)
952                                         att = new XMLAttributes ();
953
954                                 att.CompareTo (document, parent, oatt);
955                                 counters.AddPartialToTotal (att.Counters);
956                                 if (oatt != null && oatt.IsTodo) {
957                                         counters.Todo++;
958                                         counters.ErrorTotal++;
959                                         AddAttribute (parent, "error", "todo");
960                                 }
961                         }
962
963                         if (access == null)
964                                 return;
965
966                         XMLMember member = (XMLMember) other;
967                         string acc = access [name] as string;
968                         if (acc == null)
969                                 return;
970
971                         string oacc = null;
972                         if (member.access != null)
973                                 oacc = member.access [name] as string;
974
975                         string accName = ConvertToString (Int32.Parse (acc));
976                         string oaccName = "";
977                         if (oacc != null)
978                                 oaccName = ConvertToString (Int32.Parse (oacc));
979
980                         AddWarning (parent, "Incorrect attributes: '{0}' != '{1}'", accName, oaccName);
981                 }
982
983                 protected virtual string ConvertToString (int att)
984                 {
985                         return null;
986                 }
987         }
988         
989         class XMLFields : XMLMember
990         {
991                 Hashtable fieldTypes;
992
993                 protected override void LoadExtraData (string name, XmlNode node)
994                 {
995                         XmlAttribute xatt = node.Attributes ["fieldtype"];
996                         if (xatt != null) {
997                                 if (fieldTypes == null)
998                                         fieldTypes = new Hashtable ();
999
1000                                 fieldTypes [name] = xatt.Value;
1001                         }
1002
1003                         base.LoadExtraData (name, node);
1004                 }
1005
1006                 protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other)
1007                 {
1008                         base.CompareToInner (name, parent, other);
1009                         if (fieldTypes == null)
1010                                 return;
1011
1012                         XMLFields fields = (XMLFields) other;
1013                         string ftype = fieldTypes [name] as string;
1014                         string oftype = null;
1015                         if (fields.fieldTypes != null)
1016                                 oftype = fields.fieldTypes [name] as string;
1017
1018                         AddWarning (parent, "Field type is {0} and should be {1}", oftype, ftype);
1019                 }
1020
1021                 protected override string ConvertToString (int att)
1022                 {
1023                         FieldAttributes fa = (FieldAttributes) att;
1024                         return fa.ToString ();
1025                 }
1026
1027                 public override string GroupName {
1028                         get { return "fields"; }
1029                 }
1030
1031                 public override string Name {
1032                         get { return "field"; }
1033                 }
1034         }
1035
1036         class XMLProperties : XMLMember
1037         {
1038                 Hashtable nameToMethod = new Hashtable ();
1039
1040                 protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other)
1041                 {
1042                         XMLProperties oprop = other as XMLProperties;
1043                         if (oprop != null) {
1044                                 XMLMethods m = nameToMethod [name] as XMLMethods;
1045                                 XMLMethods om = oprop.nameToMethod [name] as XMLMethods;
1046                                 if (m != null || om != null) {
1047                                         if (m == null)
1048                                                 m = new XMLMethods ();
1049
1050                                         Counters copy = counters;
1051                                         m.CompareTo (document, parent, om);
1052                                         counters = new Counters ();
1053                                         counters.AddPartialToPartial (m.Counters);
1054                                         AddCountersAttributes (parent);
1055                                         counters = copy;
1056                                         counters.AddPartialToPartial (m.Counters);
1057                                 }
1058                         }
1059
1060                         base.CompareToInner (name, parent, other);
1061                 }
1062
1063                 protected override void LoadExtraData (string name, XmlNode node)
1064                 {
1065                         XmlNode orig = node;
1066                         while (node != null) {
1067                                 if (node != null && node.Name == "methods") {
1068                                         XMLMethods m = new XMLMethods ();
1069                                         XmlNode parent = node.ParentNode;
1070                                         string key = GetNodeKey (name, parent);
1071                                         m.LoadData (node);
1072                                         nameToMethod [key] = m;
1073                                         break;
1074                                 }
1075                                 node = node.NextSibling;
1076                         }
1077
1078                         base.LoadExtraData (name, orig);
1079                 }
1080
1081                 public override string GetNodeKey (string name, XmlNode node)
1082                 {
1083                         XmlAttributeCollection atts = node.Attributes;
1084                         return String.Format ("{0}:{1}:{2}", atts ["name"].Value,
1085                                                              atts ["ptype"].Value,
1086                                                              atts  ["params"].Value);
1087                 }
1088
1089                 public override string GroupName {
1090                         get { return "properties"; }
1091                 }
1092
1093                 public override string Name {
1094                         get { return "property"; }
1095                 }
1096         }
1097
1098         class XMLEvents : XMLMember
1099         {
1100                 Hashtable eventTypes;
1101
1102                 public override void CompareTo (XmlDocument doc, XmlNode parent, object other)
1103                 {
1104                         base.CompareTo (doc, parent, other);
1105                         AddCountersAttributes (parent);
1106                 }
1107
1108                 protected override void LoadExtraData (string name, XmlNode node)
1109                 {
1110                         XmlAttribute xatt = node.Attributes ["eventtype"];
1111                         if (xatt != null) {
1112                                 if (eventTypes == null)
1113                                         eventTypes = new Hashtable ();
1114
1115                                 eventTypes [name] = xatt.Value;
1116                         }
1117
1118                         base.LoadExtraData (name, node);
1119                 }
1120
1121                 protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other)
1122                 {
1123                         base.CompareToInner (name, parent, other);
1124                         if (eventTypes == null)
1125                                 return;
1126
1127                         XMLEvents evt = (XMLEvents) other;
1128                         string etype = eventTypes [name] as string;
1129                         string oetype = null;
1130                         if (evt.eventTypes != null)
1131                                 oetype = evt.eventTypes [name] as string;
1132
1133                         AddWarning (parent, "Event type is {0} and should be {1}", oetype, etype);
1134                 }
1135
1136                 protected override string ConvertToString (int att)
1137                 {
1138                         EventAttributes ea = (EventAttributes) att;
1139                         return ea.ToString ();
1140                 }
1141
1142                 public override string GroupName {
1143                         get { return "events"; }
1144                 }
1145
1146                 public override string Name {
1147                         get { return "event"; }
1148                 }
1149         }
1150
1151         class XMLMethods : XMLMember
1152         {
1153                 Hashtable returnTypes;
1154
1155                 protected override void LoadExtraData (string name, XmlNode node)
1156                 {
1157                         XmlAttribute xatt = node.Attributes ["returntype"];
1158                         if (xatt != null) {
1159                                 if (returnTypes == null)
1160                                         returnTypes = new Hashtable ();
1161
1162                                 returnTypes [name] = xatt.Value;
1163                         }
1164
1165                         base.LoadExtraData (name, node);
1166                 }
1167
1168                 protected override void CompareToInner (string name, XmlNode parent, XMLNameGroup other)
1169                 {
1170                         base.CompareToInner (name, parent, other);
1171                         if (returnTypes == null)
1172                                 return;
1173
1174                         XMLMethods methods = (XMLMethods) other;
1175                         string rtype = returnTypes [name] as string;
1176                         string ortype = null;
1177                         if (methods.returnTypes != null)
1178                                 ortype = methods.returnTypes [name] as string;
1179
1180                         AddWarning (parent, "Event type is {0} and should be {1}", ortype, rtype);
1181                 }
1182
1183                 protected override string ConvertToString (int att)
1184                 {
1185                         MethodAttributes ma = (MethodAttributes) att;
1186                         return ma.ToString ();
1187                 }
1188
1189                 public override string GroupName {
1190                         get { return "methods"; }
1191                 }
1192
1193                 public override string Name {
1194                         get { return "method"; }
1195                 }
1196         }
1197
1198         class XMLConstructors : XMLMethods
1199         {
1200                 public override string GroupName {
1201                         get { return "constructors"; }
1202                 }
1203
1204                 public override string Name {
1205                         get { return "constructor"; }
1206                 }
1207         }
1208
1209         class XmlNodeComparer : IComparer
1210         {
1211                 public static XmlNodeComparer Default = new XmlNodeComparer ();
1212
1213                 public int Compare (object a, object b)
1214                 {
1215                         XmlNode na = (XmlNode) a;
1216                         XmlNode nb = (XmlNode) b;
1217                         return String.Compare (na.Attributes ["name"].Value, nb.Attributes ["name"].Value);
1218                 }
1219         }
1220 }
1221