Merge pull request #1695 from gregoryyoung/master
[mono.git] / mcs / tools / corcompare / mono-api-html / MemberComparer.cs
1 // 
2 // Authors
3 //    Sebastien Pouliot  <sebastien@xamarin.com>
4 //
5 // Copyright 2013-2014 Xamarin Inc. http://www.xamarin.com
6 // 
7 // Permission is hereby granted, free of charge, to any person obtaining
8 // a copy of this software and associated documentation files (the
9 // "Software"), to deal in the Software without restriction, including
10 // without limitation the rights to use, copy, modify, merge, publish,
11 // distribute, sublicense, and/or sell copies of the Software, and to
12 // permit persons to whom the Software is furnished to do so, subject to
13 // the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be
16 // included in all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 //
26
27 using System;
28 using System.Collections.Generic;
29 using System.Linq;
30 using System.Reflection;
31 using System.Text;
32 using System.Xml.Linq;
33
34 namespace Xamarin.ApiDiff {
35
36         public abstract class MemberComparer : Comparer {
37
38                 // true if this is the first element being added or removed in the group being rendered
39                 protected bool first;
40
41                 public abstract string GroupName { get; }
42                 public abstract string ElementName { get; }
43
44                 public void Compare (XElement source, XElement target)
45                 {
46                         var s = source.Element (GroupName);
47                         var t = target.Element (GroupName);
48                         if (XNode.DeepEquals (s, t))
49                                 return;
50
51                         if (s == null) {
52                                 Add (t.Elements (ElementName));
53                         } else if (t == null) {
54                                 Remove (s.Elements (ElementName));
55                         } else {
56                                 Compare (s.Elements (ElementName), t.Elements (ElementName));
57                         }
58                 }
59
60                 public override void SetContext (XElement current)
61                 {
62                 }
63
64                 public XElement Source { get; set; }
65
66                 public virtual bool Find (XElement e)
67                 {
68                         return e.GetAttribute ("name") == Source.GetAttribute ("name");
69                 }
70
71                 XElement Find (IEnumerable<XElement> target)
72                 {
73                         return State.Lax ? target.FirstOrDefault (Find) : target.SingleOrDefault (Find);
74                 }
75
76                 public override void Compare (IEnumerable<XElement> source, IEnumerable<XElement> target)
77                 {
78                         removed.Clear ();
79                         modified.Clear ();
80
81                         foreach (var s in source) {
82                                 SetContext (s);
83                                 Source = s;
84                                 var t = Find (target);
85                                 if (t == null) {
86                                         // not in target, it was removed
87                                         removed.Add (s);
88                                 } else {
89                                         t.Remove ();
90                                         // possibly modified
91                                         if (Equals (s, t, modified))
92                                                 continue;
93
94                                         Modified (s, t, modified);
95                                 }
96                         }
97                         // delayed, that way we show "Modified", "Added" and then "Removed"
98                         Remove (removed);
99
100                         Modify (modified);
101
102                         // remaining == newly added in target
103                         Add (target);
104                 }
105
106                 void Add (IEnumerable<XElement> elements)
107                 {
108                         bool a = false;
109                         foreach (var item in elements) {
110                                 SetContext (item);
111                                 if (State.IgnoreAdded.Any (re => re.IsMatch (GetDescription (item))))
112                                         continue;
113                                 if (!a) {
114                                         BeforeAdding (elements);
115                                         a = true;
116                                 }
117                                 Added (item);
118                         }
119                         if (a)
120                                 AfterAdding ();
121                 }
122
123                 void Modify (ApiChanges modified)
124                 {
125                         foreach (var changes in modified) {
126                                 Output.WriteLine ("<p>{0}:</p>", changes.Key);
127                                 Output.WriteLine ("<pre>");
128                                 foreach (var element in changes.Value) {
129                                         foreach (var line in element.Member.ToString ().Split ('\n'))
130                                                 Output.WriteLine ("\t{0}", line);
131
132                                 }
133                                 Output.WriteLine ("</pre>");
134                         }
135                 }
136
137                 void Remove (IEnumerable<XElement> elements)
138                 {
139                         bool r = false;
140                         foreach (var item in elements) {
141                                 if (State.IgnoreRemoved.Any (re => re.IsMatch (GetDescription (item))))
142                                         continue;
143                                 SetContext (item);
144                                 if (!r) {
145                                         BeforeRemoving (elements);
146                                         r = true;
147                                 }
148                                 Removed (item);
149                         }
150                         if (r)
151                                 AfterRemoving ();
152                 }
153                         
154                 public abstract string GetDescription (XElement e);
155
156                 protected StringBuilder GetObsoleteMessage (XElement e)
157                 {
158                         var sb = new StringBuilder ();
159                         string o = e.GetObsoleteMessage ();
160                         if (o != null) {
161                                 sb.Append ("[Obsolete");
162                                 if (o.Length > 0)
163                                         sb.Append (" (\"").Append (o).Append ("\")");
164                                 sb.AppendLine ("]");
165                                 for (int i = 0; i < State.Indent + 1; i++)
166                                         sb.Append ('\t');
167                         }
168                         return sb;
169                 }
170
171                 public override bool Equals (XElement source, XElement target, ApiChanges changes)
172                 {
173                         RenderAttributes (source, target, changes);
174
175                         // We don't want to compare attributes.
176                         RemoveAttributes (source);
177                         RemoveAttributes (target);
178
179                         return base.Equals (source, target, changes);
180                 }
181
182                 public virtual void BeforeAdding (IEnumerable<XElement> list)
183                 {
184                         first = true;
185                         Output.WriteLine ("<p>Added {0}:</p><pre>", list.Count () > 1 ? GroupName : ElementName);
186                         if (State.Colorize)
187                                 Output.Write ("<font color='green'>");
188                 }
189
190                 public override void Added (XElement target)
191                 {
192                         var o = GetObsoleteMessage (target);
193                         if (!first && (o.Length > 0))
194                                 Output.WriteLine ();
195                         Indent ().WriteLine ("\t{0}{1}", o, GetDescription (target));
196                         first = false;
197                 }
198
199                 public virtual void AfterAdding ()
200                 {
201                         if (State.Colorize)
202                                 Output.Write ("</font>");
203                         Output.WriteLine ("</pre>");
204                 }
205
206                 public override void Modified (XElement source, XElement target, ApiChanges change)
207                 {
208                 }
209
210                 public virtual void BeforeRemoving (IEnumerable<XElement> list)
211                 {
212                         first = true;
213                         Output.WriteLine ("<p>Removed {0}:</p>\n<pre>", list.Count () > 1 ? GroupName : ElementName);
214                         if (State.Colorize)
215                                 Output.Write ("<font color='red'>");
216                 }
217
218                 public override void Removed (XElement source)
219                 {
220                         var o = GetObsoleteMessage (source);
221                         if (!first && (o.Length > 0))
222                                 Output.WriteLine ();
223                         Indent ().WriteLine ("\t{0}{1}", o, GetDescription (source));
224                         first = false;
225                 }
226
227                 public virtual void AfterRemoving ()
228                 {
229                         if (State.Colorize)
230                                 Output.Write ("</font>");
231                         Output.WriteLine ("</pre>");
232                 }
233
234                 string RenderGenericParameter (XElement gp)
235                 {
236                         var sb = new StringBuilder ();
237                         sb.Append (gp.GetTypeName ("name"));
238
239                         var constraints = gp.DescendantList ("generic-parameter-constraints", "generic-parameter-constraint");
240                         if (constraints != null && constraints.Count > 0) {
241                                 sb.Append (" : ");
242                                 for (int i = 0; i < constraints.Count; i++) {
243                                         if (i > 0)
244                                                 sb.Append (", ");
245                                         sb.Append (constraints [i].GetTypeName ("name"));
246                                 }
247                         }
248                         return sb.ToString ();
249                 }
250
251                 protected void RenderGenericParameters (XElement source, XElement target, ApiChange change)
252                 {
253                         var src = source.DescendantList ("generic-parameters", "generic-parameter");
254                         var tgt = target.DescendantList ("generic-parameters", "generic-parameter");
255                         var srcCount = src == null ? 0 : src.Count;
256                         var tgtCount = tgt == null ? 0 : tgt.Count;
257
258                         if (srcCount == 0 && tgtCount == 0)
259                                 return;
260
261                         change.Append ("&lt;");
262                         for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
263                                 if (i > 0)
264                                         change.Append (", ");
265                                 if (i >= srcCount) {
266                                         change.AppendAdded (RenderGenericParameter (tgt [i]), true);
267                                 } else if (i >= tgtCount) {
268                                         change.AppendRemoved (RenderGenericParameter (src [i]), true);
269                                 } else {
270                                         var srcName = RenderGenericParameter (src [i]);
271                                         var tgtName = RenderGenericParameter (tgt [i]);
272
273                                         if (srcName != tgtName) {
274                                                 change.AppendModified (srcName, tgtName, true);
275                                         } else {
276                                                 change.Append (srcName);
277                                         }
278                                         }
279                                 }
280                         change.Append ("&gt;");
281                 }
282
283                 protected string FormatValue (string type, string value)
284                 {
285                         if (value == null)
286                                 return "null";
287
288                         if (type == "string")
289                                 return "\"" + value + "\"";
290                         else if (type == "bool") {
291                                 switch (value) {
292                                 case "True":
293                                         return "true";
294                                 case "False":
295                                         return "false";
296                                 default:
297                                         return value;
298                                 }
299                         }
300
301                         return value;
302                 }
303
304                 protected void RenderParameters (XElement source, XElement target, ApiChange change)
305                 {
306                         var src = source.DescendantList ("parameters", "parameter");
307                         var tgt = target.DescendantList ("parameters", "parameter");
308                         var srcCount = src == null ? 0 : src.Count;
309                         var tgtCount = tgt == null ? 0 : tgt.Count;
310
311                         change.Append (" (");
312                         for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
313                                 if (i > 0)
314                                         change.Append (", ");
315
316                                 if (i >= srcCount) {
317                                         change.AppendAdded (tgt [i].GetTypeName ("type") + " " + tgt [i].GetAttribute ("name"), true);
318                                 } else if (i >= tgtCount) {
319                                         change.AppendRemoved (src [i].GetTypeName ("type") + " " + src [i].GetAttribute ("name"), true);
320                                 } else {
321                                         var paramSourceType = src [i].GetTypeName ("type");
322                                         var paramTargetType = tgt [i].GetTypeName ("type");
323
324                                         var paramSourceName = src [i].GetAttribute ("name");
325                                         var paramTargetName = tgt [i].GetAttribute ("name");
326
327                                         if (paramSourceType != paramTargetType) {
328                                                 change.AppendModified (paramSourceType, paramTargetType, true);
329                                         } else {
330                                                 change.Append (paramSourceType);
331                                         }
332                                         change.Append (" ");
333                                         if (paramSourceName != paramTargetName) {
334                                                 change.AppendModified (paramSourceName, paramTargetName, false);
335                                         } else {
336                                                 change.Append (paramSourceName);
337                                         }
338
339                                         var optSource = src [i].Attribute ("optional");
340                                         var optTarget = tgt [i].Attribute ("optional");
341                                         var srcValue = FormatValue (paramSourceType, src [i].GetAttribute ("defaultValue"));
342                                         var tgtValue = FormatValue (paramTargetType, tgt [i].GetAttribute ("defaultValue"));
343
344                                         if (optSource != null) {
345                                                 if (optTarget != null) {
346                                                         change.Append (" = ");
347                                                         if (srcValue != tgtValue) {
348                                                                 change.AppendModified (srcValue, tgtValue, false);
349                                                         } else {
350                                                                 change.Append (tgtValue);
351                                                         }
352                                                 } else {
353                                                         change.AppendRemoved (" = " + srcValue);
354                                                 }
355                                         } else {
356                                                 if (optTarget != null)
357                                                         change.AppendAdded (" = " + tgtValue);
358                                         }
359                                 }
360                         }
361
362                         change.Append (")");
363
364                         // Ignore any parameter name changes if requested.
365                         if (State.IgnoreParameterNameChanges && !change.Breaking) {
366                                 change.AnyChange = false;
367                                 change.HasIgnoredChanges = true;
368                         }
369                 }
370
371                 void RenderVTable (MethodAttributes source, MethodAttributes target, ApiChange change)
372                 {
373                         var srcAbstract = (source & MethodAttributes.Abstract) == MethodAttributes.Abstract;
374                         var tgtAbstract = (target & MethodAttributes.Abstract) == MethodAttributes.Abstract;
375                         var srcFinal = (source & MethodAttributes.Final) == MethodAttributes.Final;
376                         var tgtFinal = (target & MethodAttributes.Final) == MethodAttributes.Final;
377                         var srcVirtual = (source & MethodAttributes.Virtual) == MethodAttributes.Virtual;
378                         var tgtVirtual = (target & MethodAttributes.Virtual) == MethodAttributes.Virtual;
379                         var srcOverride = (source & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
380                         var tgtOverride = (target & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
381
382                         var srcWord = srcVirtual ? (srcOverride ? "override" : "virtual") : string.Empty;
383                         var tgtWord = tgtVirtual ? (tgtOverride ? "override" : "virtual") : string.Empty;
384                         var breaking = srcWord.Length > 0 && tgtWord.Length == 0;
385
386                         if (srcAbstract) {
387                                 if (tgtAbstract) {
388                                         change.Append ("abstract ");
389                                 } else if (tgtVirtual) {
390                                         change.AppendModified ("abstract", tgtWord, false).Append (" ");
391                                 } else {
392                                         change.AppendRemoved ("abstract").Append (" ");
393                                 }
394                         } else {
395                                 if (tgtAbstract) {
396                                         change.AppendAdded ("abstract", true).Append (" ");
397                                 } else if (srcWord != tgtWord) {
398                                         if (!tgtFinal)
399                                                 change.AppendModified (srcWord, tgtWord, breaking).Append (" ");
400                                 } else if (tgtWord.Length > 0) {
401                                         change.Append (tgtWord).Append (" ");
402                                 } else if (srcWord.Length > 0) {
403                                         change.AppendRemoved (srcWord, breaking).Append (" ");
404                                 }
405                         }
406
407                         if (srcFinal) {
408                                 if (tgtFinal) {
409                                         change.Append ("final ");
410                                 } else {
411                                         change.AppendRemoved ("final", false).Append (" "); // removing 'final' is not a breaking change.
412                                 }
413                         } else {
414                                 if (tgtFinal && srcVirtual) {
415                                         change.AppendModified ("virtual", "final", true).Append (" "); // adding 'final' is a breaking change if the member was virtual
416                                 }
417                         }
418
419                         if (!srcVirtual && !srcFinal && tgtVirtual && tgtFinal) {
420                                 // existing member implements a member from a new interface
421                                 // this would show up as 'virtual final', which is redundant, so show nothing at all.
422                                 change.HasIgnoredChanges = true;
423                         }
424
425                         // Ignore non-breaking virtual changes.
426                         if (State.IgnoreVirtualChanges && !change.Breaking) {
427                                 change.AnyChange = false;
428                                 change.HasIgnoredChanges = true;
429                         }
430                 }
431
432                 protected string GetVisibility (MethodAttributes attr)
433                 {
434                         switch (attr) {
435                         case MethodAttributes.Private:
436                         case MethodAttributes.PrivateScope:
437                                 return "private";
438                         case MethodAttributes.Assembly:
439                                 return "internal";
440                         case MethodAttributes.FamANDAssem:
441                                 return "private internal";
442                         case MethodAttributes.FamORAssem:
443                                 return "protected"; // customers don't care about 'internal';
444                         case MethodAttributes.Family:
445                                 return "protected";
446                         case MethodAttributes.Public:
447                                 return "public";
448                         default:
449                                 throw new NotImplementedException ();
450                         }
451                 }
452
453                 protected void RenderVisibility (MethodAttributes source, MethodAttributes target, ApiChange diff)
454                 {
455                         source = source & MethodAttributes.MemberAccessMask;
456                         target = target & MethodAttributes.MemberAccessMask;
457
458                         if (source == target) {
459                                 diff.Append (GetVisibility (target));
460                         } else {
461                                 var breaking = false;
462                                 switch (source) {
463                                 case MethodAttributes.Private:
464                                 case MethodAttributes.Assembly:
465                                 case MethodAttributes.FamANDAssem:
466                                         break; // these are not publicly visible, thus not breaking
467                                 case MethodAttributes.FamORAssem:
468                                 case MethodAttributes.Family:
469                                         switch (target) {
470                                         case MethodAttributes.Public:
471                                                 // to public is not a breaking change
472                                                 break;
473                                         case MethodAttributes.Family:
474                                         case MethodAttributes.FamORAssem:
475                                                 // not a breaking change, but should still show up in diff
476                                                 break;
477                                         default:
478                                                 // anything else is a breaking change
479                                                 breaking = true;
480                                                 break;
481                                         }
482                                         break;
483                                 case MethodAttributes.Public:
484                                 default:
485                                         // any change from public is breaking.
486                                         breaking = true;
487                                         break;
488                                 }
489
490                                 diff.AppendModified (GetVisibility (source), GetVisibility (target), breaking);
491                         }
492                         diff.Append (" ");
493                 }
494
495                 protected void RenderStatic (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
496                 {
497                         var srcStatic = (src & MethodAttributes.Static) == MethodAttributes.Static;
498                         var tgtStatic = (tgt & MethodAttributes.Static) == MethodAttributes.Static;
499
500                         if (srcStatic != tgtStatic) {
501                                 if (srcStatic) {
502                                         diff.AppendRemoved ("static", true).Append (" ");
503                                 } else {
504                                         diff.AppendAdded ("static", true).Append (" ");
505                                 }
506                         }
507                 }
508
509                 protected void RenderMethodAttributes (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
510                 {
511                         RenderStatic (src, tgt, diff);
512                         RenderVisibility (src & MethodAttributes.MemberAccessMask, tgt & MethodAttributes.MemberAccessMask, diff);
513                         RenderVTable (src, tgt, diff);
514                 }
515
516                 protected void RenderMethodAttributes (XElement source, XElement target, ApiChange diff)
517                 {
518                         RenderMethodAttributes (source.GetMethodAttributes (), target.GetMethodAttributes (), diff);
519                 }
520
521                 protected void RemoveAttributes (XElement element)
522                 {
523                         var srcAttributes = element.Element ("attributes");
524                         if (srcAttributes != null)
525                                 srcAttributes.Remove ();
526
527                         foreach (var el in element.Elements ())
528                                 RemoveAttributes (el);
529                 }
530
531                 protected void RenderAttributes (XElement source, XElement target, ApiChanges changes)
532                 {
533                         var srcObsolete = source.GetObsoleteMessage ();
534                         var tgtObsolete = target.GetObsoleteMessage ();
535
536                         if (srcObsolete == tgtObsolete)
537                                 return; // nothing changed
538
539                         if (srcObsolete == null) {
540                                 if (tgtObsolete == null)
541                                         return; // neither is obsolete
542                                 var change = new ApiChange ();
543                                 change.Header = "Obsoleted " + GroupName;
544                                 if (State.Colorize)
545                                         change.Append ("<font color='gray'>");
546                                 change.Append ("[Obsolete (");
547                                 if (tgtObsolete != string.Empty)
548                                         change.Append ("\"").Append (tgtObsolete).Append ("\"");
549                                 change.Append ("]\n");
550                                 change.Append (GetDescription (target));
551                                 if (State.Colorize)
552                                         change.Append ("</font>");
553                                 change.AnyChange = true;
554                                 changes.Add (source, target, change);
555                         } else if (tgtObsolete == null) {
556                                 // Made non-obsolete. Do we care to report this?
557                         } else {
558                                 // Obsolete message changed. Do we care to report this?
559                         }
560                 }
561
562                 protected void RenderName (XElement source, XElement target, ApiChange change)
563                 {
564                         var name = target.GetAttribute ("name");
565                         // show the constructor as it would be defined in C#
566                         name = name.Replace (".ctor", State.Type);
567
568                         var p = name.IndexOf ('(');
569                         if (p >= 0)
570                                 name = name.Substring (0, p);
571
572                         change.Append (name);
573                 }
574
575         }
576 }