bf14a24cc4230c7b5d2e28caf38d916dc84882c8
[mono.git] / mcs / class / monodoc / Monodoc / providers / EcmaDoc.cs
1 using System;
2 using System.Linq;
3 using System.IO;
4 using System.Text;
5 using System.Xml;
6 using System.Xml.Linq;
7 using System.Collections.Generic;
8
9 using Monodoc.Ecma;
10
11 namespace Monodoc.Providers
12 {
13         public enum EcmaNodeType {
14                 Invalid,
15                 Namespace,
16                 Type,
17                 Member,
18                 Meta, // A node that's here to serve as a header for other node
19         }
20
21
22
23         // Common functionality between ecma-provider and ecmauncompiled-provider
24         internal class EcmaDoc
25         {
26                 static EcmaUrlParser parser = new EcmaUrlParser ();
27
28                 public static void PopulateTreeFromIndexFile (string indexFilePath,
29                                                               string idPrefix,
30                                                               Tree tree,
31                                                               IDocStorage storage,
32                                                               Dictionary<string, XElement> nsSummaries,
33                                                               Func<XElement, string> indexGenerator = null,
34                                                               IEcmaProviderFileSource fileSource = null)
35                 {
36                         fileSource = fileSource ?? DefaultEcmaProviderFileSource.Default;
37                         var root = tree.RootNode;
38                         int resID = 0;
39                         var asm = Path.GetDirectoryName (indexFilePath);
40
41                         storage = storage ?? new Storage.NullStorage ();
42                         // nsSummaries is allowed to be null if the user doesn't care about it
43                         nsSummaries = nsSummaries ?? new Dictionary<string, XElement> ();
44                         // default index generator uses a counter
45                         indexGenerator = indexGenerator ?? (_ => resID++.ToString ());
46
47                         using (var reader = fileSource.GetIndexReader (indexFilePath)) {
48                                 reader.ReadToFollowing ("Types");
49                                 var types = XElement.Load (reader.ReadSubtree ());
50
51                                 foreach (var ns in types.Elements ("Namespace")) {
52                                         var nsName = (string)ns.Attribute ("Name");
53                                         nsName = !string.IsNullOrEmpty (nsName) ? nsName : "global";
54                                         var nsNode = root.GetOrCreateNode (nsName, "N:" + nsName);
55
56                                         XElement nsElements;
57                                         if (!nsSummaries.TryGetValue (nsName, out nsElements))
58                                                 nsSummaries[nsName] = nsElements = new XElement ("elements",
59                                                                                                  new XElement ("summary"),
60                                                                                                  new XElement ("remarks"));
61                                 //Add namespace summary and remarks data from file, if available
62                                         var nsFileName = fileSource.GetNamespaceXmlPath(asm, nsName);
63                                         
64                                         if(File.Exists(nsFileName)){
65                                                 var nsEl = fileSource.GetNamespaceElement (nsFileName);
66
67                                                 nsElements.Element ("summary").ReplaceWith (nsEl.Descendants ("summary").First ());
68                                                 nsElements.Element ("remarks").ReplaceWith (nsEl.Descendants ("remarks").First ());
69                                         }else{
70                                                 Console.WriteLine ("Error reading namespace XML for {0} at {1}", nsName, nsFileName);
71                                         }
72                                
73                                         foreach (var type in ns.Elements ("Type")) {
74                                                 // Add the XML file corresponding to the type to our storage
75                                                 var id = indexGenerator (type);
76                                                 string typeFilePath;
77                                                 var typeDocument = EcmaDoc.LoadTypeDocument (asm, nsName, type.Attribute ("Name").Value, out typeFilePath, fileSource);
78                                                 if (typeDocument == null)
79                                                         continue;
80
81                                                 // write the document (which may have been modified by the fileSource) to the storage
82                                                 MemoryStream io = new MemoryStream ();
83                                                 using (var writer = XmlWriter.Create (io)) {
84                                                         typeDocument.WriteTo (writer);
85                                                 }
86                                                 io.Seek (0, SeekOrigin.Begin);
87                                                 storage.Store (id, io);
88
89                                                 nsElements.Add (ExtractClassSummary (typeDocument));
90
91                                                 var typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type);
92                                                 var url = idPrefix + id + '#' + typeCaption + '/';
93                                                 typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type, true);
94                                                 var typeNode = nsNode.CreateNode (typeCaption, url);
95
96                                                 // Add meta "Members" node
97                                                 typeNode.CreateNode ("Members", "*");
98                                                 var membersNode = typeDocument.Root.Element ("Members");
99                                                 if (membersNode == null || !membersNode.Elements ().Any ())
100                                                         continue;
101                                                 var members = membersNode
102                                                         .Elements ("Member")
103                                                         .ToLookup (EcmaDoc.GetMemberType);
104
105                                                 foreach (var memberType in members) {
106                                                         // We pluralize the member type to get the caption and take the first letter as URL
107                                                         var node = typeNode.CreateNode (EcmaDoc.PluralizeMemberType (memberType.Key), memberType.Key[0].ToString ());
108                                                         var memberIndex = 0;
109
110                                                         var isCtors = memberType.Key[0] == 'C';
111
112                                                         // We do not escape much member name here
113                                                         foreach (var memberGroup in memberType.GroupBy (m => MakeMemberCaption (m, isCtors))) {
114                                                                 if (memberGroup.Count () > 1) {
115                                                                         // Generate overload
116                                                                         var overloadCaption = MakeMemberCaption (memberGroup.First (), false);
117                                                                         var overloadNode = node.CreateNode (overloadCaption, overloadCaption);
118                                                                         foreach (var member in memberGroup)
119                                                                                 overloadNode.CreateNode (MakeMemberCaption (member, true), (memberIndex++).ToString ());
120                                                                         overloadNode.Sort ();
121                                                                 } else {
122                                                                         // We treat constructor differently by showing their argument list in all cases
123                                                                         node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
124                                                                 }
125                                                         }
126                                                         node.Sort ();
127                                                 }
128                                         }
129
130                                         nsNode.Sort ();
131                                 }
132                                 root.Sort ();
133                         }
134                 }
135
136                 // Utility methods
137
138                 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, IEcmaProviderFileSource fileSource = null)
139                 {
140                         string dummy;
141                         return LoadTypeDocument (basePath, nsName, typeName, out dummy, fileSource ?? DefaultEcmaProviderFileSource.Default);
142                 }
143
144                 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, out string finalPath, IEcmaProviderFileSource fileSource = null)
145                 {
146                         fileSource = fileSource ?? DefaultEcmaProviderFileSource.Default;
147
148                         finalPath = fileSource.GetTypeXmlPath (basePath, nsName, typeName);
149                         if (!File.Exists (finalPath)) {
150                                 Console.Error.WriteLine ("Warning: couldn't process type file `{0}' as it doesn't exist", finalPath);
151                                 return null;
152                         }
153
154                         XDocument doc = null;
155                         try {
156                                 doc = fileSource.GetTypeDocument(finalPath);
157                         } catch (Exception e) {
158                                 Console.WriteLine ("Document `{0}' is unparsable, {1}", finalPath, e.ToString ());
159                         }
160
161                         return doc;
162                 }
163
164                 public static string GetTypeCaptionFromIndex (XElement typeNodeFromIndex, bool full = false)
165                 {
166                         var t = typeNodeFromIndex;
167                         var c = ((string)(t.Attribute ("DisplayName") ?? t.Attribute ("Name"))).Replace ('+', '.');
168                         if (full)
169                                 c += " " + (string)t.Attribute ("Kind");
170                         return c;
171                 }
172
173                 public static string PluralizeMemberType (string memberType)
174                 {
175                         switch (memberType) {
176                         case "Property":
177                                 return "Properties";
178                         default:
179                                 return memberType + "s";
180                         }
181                 }
182
183                 public static string GetMemberType (XElement m)
184                 {
185                         return m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value;
186                 }
187
188                 public static string MakeMemberCaption (XElement member, bool withArguments)
189                 {
190                         var caption = (string)member.Attribute ("MemberName");
191                         // Use type name instead of .ctor for cosmetic sake
192                         if (caption == ".ctor") {
193                                 caption = (string)member.Ancestors ("Type").First ().Attribute ("Name");
194                                 // If this is an inner type ctor, strip the parent type reference
195                                 var plusIndex = caption.LastIndexOf ('+');
196                                 if (plusIndex != -1)
197                                         caption = caption.Substring (plusIndex + 1);
198                         }
199                         if (caption.StartsWith ("op_")) {
200                                 string sig;
201                                 caption = MakeOperatorSignature (member, out sig);
202                                 caption = withArguments ? sig : caption;
203                                 return caption;
204                         }
205                         if (withArguments) {
206                                 var args = member.Element ("Parameters");
207                                 caption += '(';
208                                 if (args != null && args.Elements ("Parameter").Any ()) {
209                                         caption += args.Elements ("Parameter")
210                                                 .Select (p => (string)p.Attribute ("Type"))
211                                                 .Aggregate ((p1, p2) => p1 + "," + p2);
212                                 }
213                                 caption += ')';
214                         }
215                         
216                         return caption;
217                 }
218
219                 public static Node MatchNodeWithEcmaUrl (string url, Tree tree)
220                 {
221                         Node result = null;
222                         EcmaDesc desc;
223                         if (!parser.TryParse (url, out desc))
224                                 return null;
225
226                         // Namespace search
227                         Node currentNode = tree.RootNode;
228                         Node searchNode = new Node () { Caption = desc.Namespace };
229                         int index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
230                         if (index >= 0)
231                                 result = currentNode.ChildNodes[index];
232                         if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
233                                 return result;
234
235                         // Type search
236                         currentNode = result;
237                         result = null;
238                         searchNode.Caption = desc.ToCompleteTypeName ();
239                         if (!desc.GenericTypeArgumentsIsNumeric)
240                                 index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
241                         else
242                                 index = GenericTypeBacktickSearch (currentNode.ChildNodes, desc);
243                         if (index >= 0)
244                                 result = currentNode.ChildNodes[index];
245                         if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
246                                 return result;
247
248                         // Member selection
249                         currentNode = result;
250                         result = null;
251                         var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
252                         currentNode = FindNodeForCaption (currentNode.ChildNodes, caption);
253                         if (currentNode == null
254                             || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
255                                 return currentNode;
256
257                         // Member search
258                         result = null;
259                         var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
260                         searchNode.Caption = desc.ToCompleteMemberName (format);
261                         index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
262                         if (index < 0)
263                                 return null;
264                         result = currentNode.ChildNodes[index];
265                         if (result.ChildNodes.Count == 0 || desc.IsEtc)
266                                 return result;
267
268                         // Overloads search
269                         currentNode = result;
270                         searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
271                         index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
272                         if (index < 0)
273                                 return result;
274                         result = result.ChildNodes[index];
275
276                         return result;
277                 }
278
279                 static int GenericTypeBacktickSearch (IList<Node> childNodes, EcmaDesc desc)
280                 {
281                         /* Our strategy is to search for the non-generic variant of the type
282                          * (which in most case should fail) and then use the closest index
283                          * to linearily search for the generic variant with the right generic arg number
284                          */
285                         var searchNode = new Node () { Caption = desc.TypeName };
286                         int index = childNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
287                         // Place the index in the right start position
288                         if (index < 0)
289                                 index = ~index;
290
291                         for (int i = index; i < childNodes.Count; i++) {
292                                 var currentNode = childNodes[i];
293                                 // Find the index of the generic argument list
294                                 int genericIndex = currentNode.Caption.IndexOf ('<');
295                                 // If we are not on the same base type name anymore, there is no point
296                                 int captionSlice = genericIndex != -1 ? genericIndex : currentNode.Caption.LastIndexOf (' ');
297                                 if (string.Compare (searchNode.Caption, 0,
298                                                     currentNode.Caption, 0,
299                                                     Math.Max (captionSlice, searchNode.Caption.Length),
300                                                     StringComparison.Ordinal) != 0)
301                                         break;
302
303                                 var numGenerics = CountTypeGenericArguments (currentNode.Caption, genericIndex);
304                                 if (numGenerics == desc.GenericTypeArguments.Count) {
305                                         // Simple comparison if we are not looking for an inner type
306                                         if (desc.NestedType == null)
307                                                 return i;
308                                         // If more complicated, we fallback to using EcmaUrlParser
309                                         var caption = currentNode.Caption;
310                                         caption = "T:" + caption.Substring (0, caption.LastIndexOf (' ')).Replace ('.', '+');
311                                         EcmaDesc otherDesc;
312                                         var parser = new EcmaUrlParser ();
313                                         if (parser.TryParse (caption, out otherDesc) && desc.NestedType.Equals (otherDesc.NestedType))
314                                                 return i;
315                                 }
316                         }
317
318                         return -1;
319                 }
320
321                 // This comparer returns the answer straight from caption comparison
322                 class EcmaGenericNodeComparer : IComparer<Node>
323                 {
324                         public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
325
326                         public int Compare (Node n1, Node n2)
327                         {
328                                 return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
329                         }
330                 }
331
332                 // This comparer take into account the space in the caption
333                 class EcmaTypeNodeComparer : IComparer<Node>
334                 {
335                         public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
336
337                         public int Compare (Node n1, Node n2)
338                         {
339                                 int length1 = CaptionLength (n1.Caption);
340                                 int length2 = CaptionLength (n2.Caption);
341
342                                 return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
343                         }
344
345                         int CaptionLength (string caption)
346                         {
347                                 var length = caption.LastIndexOf (' ');
348                                 return length == -1 ? caption.Length : length;
349                         }
350                 }
351
352                 public static Dictionary<string, string> GetContextForEcmaNode (string hash, string sourceID, Node node)
353                 {
354                         var args = new Dictionary<string, string> ();
355
356                         args["source-id"] = sourceID;
357
358                         if (node != null) {
359                                 var nodeType = GetNodeType (node);
360                                 switch (nodeType) {
361                                 case EcmaNodeType.Namespace:
362                                         args["show"] = "namespace";
363                                         args["namespace"] = node.Element.Substring ("N:".Length);
364                                         break;
365                                 case EcmaNodeType.Type:
366                                         args["show"] = "typeoverview";
367                                         break;
368                                 case EcmaNodeType.Member:
369                                 case EcmaNodeType.Meta:
370                                         switch (GetNodeMemberTypeChar (node)){
371                                         case 'C':
372                                                 args["membertype"] = "Constructor";
373                                                 break;
374                                         case 'M':
375                                                 args["membertype"] = "Method";
376                                                 break;
377                                         case 'P':
378                                                 args["membertype"] = "Property";
379                                                 break;
380                                         case 'F':
381                                                 args["membertype"] = "Field";
382                                                 break;
383                                         case 'E':
384                                                 args["membertype"] = "Event";
385                                                 break;
386                                         case 'O':
387                                                 args["membertype"] = "Operator";
388                                                 break;
389                                         case 'X':
390                                                 args["membertype"] = "ExtensionMethod";
391                                                 break;
392                                         case '*':
393                                                 args["membertype"] = "All";
394                                                 break;
395                                         }
396
397                                         if (nodeType == EcmaNodeType.Meta) {
398                                                 args["show"] = "members";
399                                                 args["index"] = "all";
400                                         } else {
401                                                 args["show"] = "member";
402                                                 args["index"] = node.Element;
403                                         }
404                                         break;
405                                 }
406                         }
407
408                         if (!string.IsNullOrEmpty (hash))
409                                 args["hash"] = hash;
410
411                         return args;
412                 }
413
414                 public static EcmaNodeType GetNodeType (Node node)
415                 {
416                         // We guess the node type by checking the depth level it's at in the tree
417                         int level = GetNodeLevel (node);
418                         switch (level) {
419                         case 0:
420                                 return EcmaNodeType.Namespace;
421                         case 1:
422                                 return EcmaNodeType.Type;
423                         case 2:
424                                 return EcmaNodeType.Meta;
425                         case 3: // Here it's either a member or, in case of overload, a meta
426                                 return node.IsLeaf ? EcmaNodeType.Member : EcmaNodeType.Meta;
427                         case 4: // At this level, everything is necessarily a member
428                                 return EcmaNodeType.Member;
429                         default:
430                                 return EcmaNodeType.Invalid;
431                         }
432                 }
433
434                 public static char GetNodeMemberTypeChar (Node node)
435                 {
436                         int level = GetNodeLevel (node);
437                         // We try to reach the member group node depending on node nested level
438                         switch (level) {
439                         case 2:
440                                 return node.Element[0];
441                         case 3:
442                                 return node.Parent.Element[0];
443                         case 4:
444                                 return node.Parent.Parent.Element[0];
445                         default:
446                                 throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
447                         }
448                 }
449
450                 public static int GetNodeLevel (Node node)
451                 {
452                         int i = 0;
453                         for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++) {
454                                 node = node.Parent;
455                                 if (node == null)
456                                         return i - 1;
457                         }
458                         return i - 1;
459                 }
460
461                 public static string EtcKindToCaption (char etc)
462                 {
463                         switch (etc) {
464                         case 'M':
465                                 return "Methods";
466                         case 'P':
467                                 return "Properties";
468                         case 'C':
469                                 return "Constructors";
470                         case 'F':
471                                 return "Fields";
472                         case 'E':
473                                 return "Events";
474                         case 'O':
475                                 return "Operators";
476                         case '*':
477                                 return "Members";
478                         default:
479                                 return null;
480                         }
481                 }
482
483                 public static string MemberKindToCaption (EcmaDesc.Kind kind)
484                 {
485                         switch (kind) {
486                         case EcmaDesc.Kind.Method:
487                                 return "Methods";
488                         case EcmaDesc.Kind.Property:
489                                 return "Properties";
490                         case EcmaDesc.Kind.Constructor:
491                                 return "Constructors";
492                         case EcmaDesc.Kind.Field:
493                                 return "Fields";
494                         case EcmaDesc.Kind.Event:
495                                 return "Events";
496                         case EcmaDesc.Kind.Operator:
497                                 return "Operators";
498                         default:
499                                 return null;
500                         }
501                 }
502
503                 public static Node FindNodeForCaption (IList<Node> nodes, string caption)
504                 {
505                         foreach (var node in nodes)
506                                 if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
507                                         return node;
508                         return null;
509                 }
510
511                 public static int CountTypeGenericArguments (string typeDefinition, int startIndex = 0)
512                 {
513                         int nestedLevel = 0;
514                         int count = 0;
515                         bool started = false;
516
517                         foreach (char c in typeDefinition.Skip (startIndex)) {
518                                 switch (c) {
519                                 case '<':
520                                         if (!started)
521                                                 count = 1;
522                                         started = true;
523                                         nestedLevel++;
524                                         break;
525                                 case ',':
526                                         if (started && nestedLevel == 1)
527                                                 count++;
528                                         break;
529                                 case '>':
530                                         nestedLevel--;
531                                         break;
532                                 }
533                         }
534
535                         return count;
536                 }
537
538                 internal static string MakeOperatorSignature (XElement member, out string memberSignature)
539                 {
540                         string name = (string)member.Attribute ("MemberName");
541                         var nicename = name.Substring(3);
542                         memberSignature = null;
543
544                         switch (name) {
545                         // unary operators: no overloading possible     [ECMA-335 §10.3.1]
546                         case "op_UnaryPlus":                    // static     R operator+       (T)
547                         case "op_UnaryNegation":                // static     R operator-       (T)
548                         case "op_LogicalNot":                   // static     R operator!       (T)
549                         case "op_OnesComplement":               // static     R operator~       (T)
550                         case "op_Increment":                    // static     R operator++      (T)
551                         case "op_Decrement":                    // static     R operator--      (T)
552                         case "op_True":                         // static  bool operator true   (T)
553                         case "op_False":                        // static  bool operator false  (T)
554                         case "op_AddressOf":                    // static     R operator&       (T)
555                         case "op_PointerDereference":           // static     R operator*       (T)
556                                 memberSignature = nicename;
557                                 break;
558                         // conversion operators: overloading based on parameter and return type [ECMA-335 §10.3.3]
559                         case "op_Implicit":                    // static implicit operator R (T)
560                         case "op_Explicit":                    // static explicit operator R (T)
561                                 nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
562                                 string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
563                                 string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
564                                 memberSignature = arg + " to " + ret;
565                                 break;
566                         // binary operators: overloading is possible [ECMA-335 §10.3.2]
567                         default:
568                                 if (member.Element ("Parameters") != null)
569                                         memberSignature =
570                                                 nicename + "("
571                                                 + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
572                                                 + ")";
573                                 break;
574                         }
575
576                         return nicename;
577                 }
578
579                 static XElement ExtractClassSummary (XDocument typeDoc)
580                 {
581                         string name = typeDoc.Root.Attribute("Name").Value;
582                         string fullName = typeDoc.Root.Attribute("FullName").Value;
583                         string assemblyName = typeDoc.Root.Element("AssemblyInfo") != null ? typeDoc.Root.Element("AssemblyInfo").Element("AssemblyName").Value : string.Empty;
584                         var docs = typeDoc.Root.Element("Docs");
585                         var summary = docs.Element("summary") ?? new XElement("summary");
586                         var remarks = docs.Element("remarks") ?? new XElement("remarks");
587                         return new XElement ("class",
588                                  new XAttribute ("name", name ?? string.Empty),
589                                  new XAttribute ("fullname", fullName ?? string.Empty),
590                                  new XAttribute ("assembly", assemblyName ?? string.Empty),
591                                  summary,
592                                  remarks);
593                 }
594         }
595 }