Merge pull request #2013 from BrzVlad/feature-concurrent-work
[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>", list.Count () > 1 ? GroupName : ElementName);
186                         Output.WriteLine (State.Colorize ? "<pre style='color: green'>" : "<pre>");
187                 }
188
189                 public override void Added (XElement target)
190                 {
191                         var o = GetObsoleteMessage (target);
192                         if (!first && (o.Length > 0))
193                                 Output.WriteLine ();
194                         Indent ().WriteLine ("\t{0}{1}", o, GetDescription (target));
195                         first = false;
196                 }
197
198                 public virtual void AfterAdding ()
199                 {
200                         Output.WriteLine ("</pre>");;
201                 }
202
203                 public override void Modified (XElement source, XElement target, ApiChanges change)
204                 {
205                 }
206
207                 public virtual void BeforeRemoving (IEnumerable<XElement> list)
208                 {
209                         first = true;
210                         Output.WriteLine ("<p>Removed {0}:</p>\n", list.Count () > 1 ? GroupName : ElementName);
211                         Output.WriteLine (State.Colorize ? "<pre style='color: red'>" : "<pre>");
212                 }
213
214                 public override void Removed (XElement source)
215                 {
216                         var o = GetObsoleteMessage (source);
217                         if (!first && (o.Length > 0))
218                                 Output.WriteLine ();
219                         Indent ().WriteLine ("\t{0}{1}", o, GetDescription (source));
220                         first = false;
221                 }
222
223                 public virtual void AfterRemoving ()
224                 {
225                         Output.WriteLine ("</pre>");;
226                 }
227
228                 string RenderGenericParameter (XElement gp)
229                 {
230                         var sb = new StringBuilder ();
231                         sb.Append (gp.GetTypeName ("name"));
232
233                         var constraints = gp.DescendantList ("generic-parameter-constraints", "generic-parameter-constraint");
234                         if (constraints != null && constraints.Count > 0) {
235                                 sb.Append (" : ");
236                                 for (int i = 0; i < constraints.Count; i++) {
237                                         if (i > 0)
238                                                 sb.Append (", ");
239                                         sb.Append (constraints [i].GetTypeName ("name"));
240                                 }
241                         }
242                         return sb.ToString ();
243                 }
244
245                 protected void RenderGenericParameters (XElement source, XElement target, ApiChange change)
246                 {
247                         var src = source.DescendantList ("generic-parameters", "generic-parameter");
248                         var tgt = target.DescendantList ("generic-parameters", "generic-parameter");
249                         var srcCount = src == null ? 0 : src.Count;
250                         var tgtCount = tgt == null ? 0 : tgt.Count;
251
252                         if (srcCount == 0 && tgtCount == 0)
253                                 return;
254
255                         change.Append ("&lt;");
256                         for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
257                                 if (i > 0)
258                                         change.Append (", ");
259                                 if (i >= srcCount) {
260                                         change.AppendAdded (RenderGenericParameter (tgt [i]), true);
261                                 } else if (i >= tgtCount) {
262                                         change.AppendRemoved (RenderGenericParameter (src [i]), true);
263                                 } else {
264                                         var srcName = RenderGenericParameter (src [i]);
265                                         var tgtName = RenderGenericParameter (tgt [i]);
266
267                                         if (srcName != tgtName) {
268                                                 change.AppendModified (srcName, tgtName, true);
269                                         } else {
270                                                 change.Append (srcName);
271                                         }
272                                         }
273                                 }
274                         change.Append ("&gt;");
275                 }
276
277                 protected string FormatValue (string type, string value)
278                 {
279                         if (value == null)
280                                 return "null";
281
282                         if (type == "string")
283                                 return "\"" + value + "\"";
284                         else if (type == "bool") {
285                                 switch (value) {
286                                 case "True":
287                                         return "true";
288                                 case "False":
289                                         return "false";
290                                 default:
291                                         return value;
292                                 }
293                         }
294
295                         return value;
296                 }
297
298                 protected void RenderParameters (XElement source, XElement target, ApiChange change)
299                 {
300                         var src = source.DescendantList ("parameters", "parameter");
301                         var tgt = target.DescendantList ("parameters", "parameter");
302                         var srcCount = src == null ? 0 : src.Count;
303                         var tgtCount = tgt == null ? 0 : tgt.Count;
304
305                         change.Append (" (");
306                         for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
307                                 if (i > 0)
308                                         change.Append (", ");
309
310                                 if (i >= srcCount) {
311                                         change.AppendAdded (tgt [i].GetTypeName ("type") + " " + tgt [i].GetAttribute ("name"), true);
312                                 } else if (i >= tgtCount) {
313                                         change.AppendRemoved (src [i].GetTypeName ("type") + " " + src [i].GetAttribute ("name"), true);
314                                 } else {
315                                         var paramSourceType = src [i].GetTypeName ("type");
316                                         var paramTargetType = tgt [i].GetTypeName ("type");
317
318                                         var paramSourceName = src [i].GetAttribute ("name");
319                                         var paramTargetName = tgt [i].GetAttribute ("name");
320
321                                         if (paramSourceType != paramTargetType) {
322                                                 change.AppendModified (paramSourceType, paramTargetType, true);
323                                         } else {
324                                                 change.Append (paramSourceType);
325                                         }
326                                         change.Append (" ");
327                                         if (paramSourceName != paramTargetName) {
328                                                 change.AppendModified (paramSourceName, paramTargetName, false);
329                                         } else {
330                                                 change.Append (paramSourceName);
331                                         }
332
333                                         var optSource = src [i].Attribute ("optional");
334                                         var optTarget = tgt [i].Attribute ("optional");
335                                         var srcValue = FormatValue (paramSourceType, src [i].GetAttribute ("defaultValue"));
336                                         var tgtValue = FormatValue (paramTargetType, tgt [i].GetAttribute ("defaultValue"));
337
338                                         if (optSource != null) {
339                                                 if (optTarget != null) {
340                                                         change.Append (" = ");
341                                                         if (srcValue != tgtValue) {
342                                                                 change.AppendModified (srcValue, tgtValue, false);
343                                                         } else {
344                                                                 change.Append (tgtValue);
345                                                         }
346                                                 } else {
347                                                         change.AppendRemoved (" = " + srcValue);
348                                                 }
349                                         } else {
350                                                 if (optTarget != null)
351                                                         change.AppendAdded (" = " + tgtValue);
352                                         }
353                                 }
354                         }
355
356                         change.Append (")");
357
358                         // Ignore any parameter name changes if requested.
359                         if (State.IgnoreParameterNameChanges && !change.Breaking) {
360                                 change.AnyChange = false;
361                                 change.HasIgnoredChanges = true;
362                         }
363                 }
364
365                 void RenderVTable (MethodAttributes source, MethodAttributes target, ApiChange change)
366                 {
367                         var srcAbstract = (source & MethodAttributes.Abstract) == MethodAttributes.Abstract;
368                         var tgtAbstract = (target & MethodAttributes.Abstract) == MethodAttributes.Abstract;
369                         var srcFinal = (source & MethodAttributes.Final) == MethodAttributes.Final;
370                         var tgtFinal = (target & MethodAttributes.Final) == MethodAttributes.Final;
371                         var srcVirtual = (source & MethodAttributes.Virtual) == MethodAttributes.Virtual;
372                         var tgtVirtual = (target & MethodAttributes.Virtual) == MethodAttributes.Virtual;
373                         var srcOverride = (source & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
374                         var tgtOverride = (target & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
375
376                         var srcWord = srcVirtual ? (srcOverride ? "override" : "virtual") : string.Empty;
377                         var tgtWord = tgtVirtual ? (tgtOverride ? "override" : "virtual") : string.Empty;
378                         var breaking = srcWord.Length > 0 && tgtWord.Length == 0;
379
380                         if (srcAbstract) {
381                                 if (tgtAbstract) {
382                                         change.Append ("abstract ");
383                                 } else if (tgtVirtual) {
384                                         change.AppendModified ("abstract", tgtWord, false).Append (" ");
385                                 } else {
386                                         change.AppendRemoved ("abstract").Append (" ");
387                                 }
388                         } else {
389                                 if (tgtAbstract) {
390                                         change.AppendAdded ("abstract", true).Append (" ");
391                                 } else if (srcWord != tgtWord) {
392                                         if (!tgtFinal)
393                                                 change.AppendModified (srcWord, tgtWord, breaking).Append (" ");
394                                 } else if (tgtWord.Length > 0) {
395                                         change.Append (tgtWord).Append (" ");
396                                 } else if (srcWord.Length > 0) {
397                                         change.AppendRemoved (srcWord, breaking).Append (" ");
398                                 }
399                         }
400
401                         if (srcFinal) {
402                                 if (tgtFinal) {
403                                         change.Append ("final ");
404                                 } else {
405                                         change.AppendRemoved ("final", false).Append (" "); // removing 'final' is not a breaking change.
406                                 }
407                         } else {
408                                 if (tgtFinal && srcVirtual) {
409                                         change.AppendModified ("virtual", "final", true).Append (" "); // adding 'final' is a breaking change if the member was virtual
410                                 }
411                         }
412
413                         if (!srcVirtual && !srcFinal && tgtVirtual && tgtFinal) {
414                                 // existing member implements a member from a new interface
415                                 // this would show up as 'virtual final', which is redundant, so show nothing at all.
416                                 change.HasIgnoredChanges = true;
417                         }
418
419                         // Ignore non-breaking virtual changes.
420                         if (State.IgnoreVirtualChanges && !change.Breaking) {
421                                 change.AnyChange = false;
422                                 change.HasIgnoredChanges = true;
423                         }
424
425                         var tgtSecurity = (source & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
426                         var srcSecurity = (target & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
427
428                         if (tgtSecurity != srcSecurity)
429                                 change.HasIgnoredChanges = true;
430
431                         var srcPInvoke = (source & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
432                         var tgtPInvoke = (target & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
433                         if (srcPInvoke != tgtPInvoke)
434                                 change.HasIgnoredChanges = true;
435                 }
436
437                 protected string GetVisibility (MethodAttributes attr)
438                 {
439                         switch (attr) {
440                         case MethodAttributes.Private:
441                         case MethodAttributes.PrivateScope:
442                                 return "private";
443                         case MethodAttributes.Assembly:
444                                 return "internal";
445                         case MethodAttributes.FamANDAssem:
446                                 return "private internal";
447                         case MethodAttributes.FamORAssem:
448                                 return "protected"; // customers don't care about 'internal';
449                         case MethodAttributes.Family:
450                                 return "protected";
451                         case MethodAttributes.Public:
452                                 return "public";
453                         default:
454                                 throw new NotImplementedException ();
455                         }
456                 }
457
458                 protected void RenderVisibility (MethodAttributes source, MethodAttributes target, ApiChange diff)
459                 {
460                         source = source & MethodAttributes.MemberAccessMask;
461                         target = target & MethodAttributes.MemberAccessMask;
462
463                         if (source == target) {
464                                 diff.Append (GetVisibility (target));
465                         } else {
466                                 var breaking = false;
467                                 switch (source) {
468                                 case MethodAttributes.Private:
469                                 case MethodAttributes.Assembly:
470                                 case MethodAttributes.FamANDAssem:
471                                         break; // these are not publicly visible, thus not breaking
472                                 case MethodAttributes.FamORAssem:
473                                 case MethodAttributes.Family:
474                                         switch (target) {
475                                         case MethodAttributes.Public:
476                                                 // to public is not a breaking change
477                                                 break;
478                                         case MethodAttributes.Family:
479                                         case MethodAttributes.FamORAssem:
480                                                 // not a breaking change, but should still show up in diff
481                                                 break;
482                                         default:
483                                                 // anything else is a breaking change
484                                                 breaking = true;
485                                                 break;
486                                         }
487                                         break;
488                                 case MethodAttributes.Public:
489                                 default:
490                                         // any change from public is breaking.
491                                         breaking = true;
492                                         break;
493                                 }
494
495                                 diff.AppendModified (GetVisibility (source), GetVisibility (target), breaking);
496                         }
497                         diff.Append (" ");
498                 }
499
500                 protected void RenderStatic (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
501                 {
502                         var srcStatic = (src & MethodAttributes.Static) == MethodAttributes.Static;
503                         var tgtStatic = (tgt & MethodAttributes.Static) == MethodAttributes.Static;
504
505                         if (srcStatic != tgtStatic) {
506                                 if (srcStatic) {
507                                         diff.AppendRemoved ("static", true).Append (" ");
508                                 } else {
509                                         diff.AppendAdded ("static", true).Append (" ");
510                                 }
511                         }
512                 }
513
514                 protected void RenderMethodAttributes (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
515                 {
516                         RenderStatic (src, tgt, diff);
517                         RenderVisibility (src & MethodAttributes.MemberAccessMask, tgt & MethodAttributes.MemberAccessMask, diff);
518                         RenderVTable (src, tgt, diff);
519                 }
520
521                 protected void RenderMethodAttributes (XElement source, XElement target, ApiChange diff)
522                 {
523                         RenderMethodAttributes (source.GetMethodAttributes (), target.GetMethodAttributes (), diff);
524                 }
525
526                 protected void RemoveAttributes (XElement element)
527                 {
528                         var srcAttributes = element.Element ("attributes");
529                         if (srcAttributes != null)
530                                 srcAttributes.Remove ();
531
532                         foreach (var el in element.Elements ())
533                                 RemoveAttributes (el);
534                 }
535
536                 protected void RenderAttributes (XElement source, XElement target, ApiChanges changes)
537                 {
538                         var srcObsolete = source.GetObsoleteMessage ();
539                         var tgtObsolete = target.GetObsoleteMessage ();
540
541                         if (srcObsolete == tgtObsolete)
542                                 return; // nothing changed
543
544                         if (srcObsolete == null) {
545                                 if (tgtObsolete == null)
546                                         return; // neither is obsolete
547                                 var change = new ApiChange ();
548                                 change.Header = "Obsoleted " + GroupName;
549                                 if (State.Colorize)
550                                         change.Append ("<span style='color:gray'>");
551                                 change.Append ("[Obsolete (");
552                                 if (tgtObsolete != string.Empty)
553                                         change.Append ("\"").Append (tgtObsolete).Append ("\"");
554                                 change.Append (")]\n");
555                                 change.Append (GetDescription (target));
556                                 if (State.Colorize)
557                                         change.Append ("</span>");
558                                 change.AnyChange = true;
559                                 changes.Add (source, target, change);
560                         } else if (tgtObsolete == null) {
561                                 // Made non-obsolete. Do we care to report this?
562                         } else {
563                                 // Obsolete message changed. Do we care to report this?
564                         }
565                 }
566
567                 protected void RenderName (XElement source, XElement target, ApiChange change)
568                 {
569                         var name = target.GetAttribute ("name");
570                         // show the constructor as it would be defined in C#
571                         name = name.Replace (".ctor", State.Type);
572
573                         var p = name.IndexOf ('(');
574                         if (p >= 0)
575                                 name = name.Substring (0, p);
576
577                         change.Append (name);
578                 }
579
580         }
581 }