17db23b2a8bb961119809affdeb856ba7244a3b2
[mono.git] / mcs / tools / tuner / Mono.Tuner / MoonlightA11yDescriptorGenerator.cs
1 //
2 // MoonlightA11yDescriptorGenerator.cs
3 //
4 // Author:
5 //   AndrĂ©s G. Aragoneses (aaragoneses@novell.com)
6 //
7 // (C) 2009 Novell, Inc.
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections;
31 using System.Collections.Generic;
32
33 using System.IO;
34 using System.Text.RegularExpressions;
35 using System.Text;
36
37 using System.Xml;
38 using System.Xml.XPath;
39
40 using Mono.Linker;
41 using Mono.Linker.Steps;
42
43 using Mono.Cecil;
44
45 namespace Mono.Tuner {
46
47         public class MoonlightA11yDescriptorGenerator : BaseStep {
48
49                 XmlTextWriter writer = null;
50                 protected override void ProcessAssembly (AssemblyDefinition assembly)
51                 {
52                         if (assembly.Name.Name == "MoonAtkBridge" || assembly.Name.Name == "System.Windows" ||
53                             assembly.Name.Name.Contains ("Dummy"))
54                                 return;
55
56                         if (writer == null) {
57                                 if (!Directory.Exists (Context.OutputDirectory))
58                                         Directory.CreateDirectory (Context.OutputDirectory);
59
60                                 string file_name = "descriptors.xml";
61                                 string file_path = Path.Combine (Context.OutputDirectory, file_name);
62                                 if (File.Exists (file_path))
63                                         File.Delete (file_path);
64                                 FileStream xml_file = new FileStream (file_path, FileMode.OpenOrCreate);
65                                 Console.WriteLine ("Created file {0}", file_name);
66                                 Console.Write ("Writing contents...");
67
68                                 writer = new XmlTextWriter (xml_file, System.Text.Encoding.UTF8);
69                                 writer.Formatting = Formatting.Indented;
70                                 writer.WriteStartElement("linker");
71                         }
72
73                         SortedDictionary <TypeDefinition, IList> types = ScanAssembly (assembly);
74                         if (types != null && types.Count > 0) {
75                                 writer.WriteStartElement("assembly");
76                                 writer.WriteAttributeString ("fullname", assembly.Name.Name);
77
78                                 foreach (TypeDefinition type in types.Keys) {
79                                         IList members = types [type];
80                                         if (members != null && members.Count > 0) {
81                                                 writer.WriteStartElement("type");
82                                                 writer.WriteAttributeString ("fullname", type.FullName);
83
84                                                 foreach (IMetadataTokenProvider member in members) {
85                                                         MethodDefinition method = member as MethodDefinition;
86                                                         if (method != null) {
87                                                                 writer.WriteStartElement("method");
88                                                                 writer.WriteAttributeString ("signature",
89                                                                                              method.ReturnType.FullName + " " +
90                                                                                              method.Name + GetMethodParams (method));
91                                                                 writer.WriteEndElement ();
92                                                                 continue;
93                                                         }
94
95                                                         FieldDefinition field = member as FieldDefinition;
96                                                         if (field != null) {
97                                                                 writer.WriteStartElement("field");
98                                                                 writer.WriteAttributeString ("signature", field.DeclaringType.FullName + " " + field.Name);
99                                                                 writer.WriteEndElement ();
100                                                         }
101                                                 }
102                                                 writer.WriteEndElement ();
103                                         }
104                                 }
105
106                                 writer.WriteEndElement ();
107                                 Console.WriteLine ();
108                         }
109
110                 }
111
112                 protected override void EndProcess ()
113                 {
114                         Console.WriteLine ();
115
116                         foreach (FileStream stream in streams)
117                                 stream.Close ();
118
119                         if (writer != null) {
120                                 writer.WriteEndElement ();
121                                 writer.Close ();
122                                 writer = null;
123                         }
124                 }
125
126                 //this is almost the ToString method of MethodDefinition...
127                 private string GetMethodParams (MethodDefinition method)
128                 {
129                         string @params = "(";
130                         if (method.HasParameters) {
131                                 for (int i = 0; i < method.Parameters.Count; i++) {
132                                         if (i > 0)
133                                                 @params += ",";
134
135                                         @params += method.Parameters [i].ParameterType.FullName;
136                                 }
137                         }
138                         @params += ")";
139                         return @params;
140                 }
141
142                 SortedDictionary<TypeDefinition, IList> /*,List<IAnnotationProvider>>*/ ScanAssembly (AssemblyDefinition assembly)
143                 {
144                         if (Annotations.GetAction (assembly) != AssemblyAction.Link)
145                                 return null;
146
147                         SortedDictionary<TypeDefinition, IList> members_used = new SortedDictionary<TypeDefinition, IList> (new TypeComparer ());
148                         foreach (TypeDefinition type in assembly.MainModule.Types) {
149                                 IList used_providers = FilterPublicMembers (ScanType (type));
150                                 if (used_providers.Count > 0)
151                                         members_used [type] = used_providers;
152                                 else if (IsInternal (type, true) &&
153                                          Annotations.IsMarked (type))
154                                         throw new NotSupportedException (String.Format ("The type {0} is used while its API is not", type.ToString ()));
155                         }
156                         return members_used;
157                 }
158
159                 IList ScanType (TypeDefinition type)
160                 {
161                         return ExtractUsedProviders (type.Methods, type.Fields);
162                 }
163
164                 static IList FilterPublicMembers (IList members)
165                 {
166                         IList new_list = new ArrayList ();
167                         foreach (MemberReference item in members)
168                                 if (IsInternal (item, true))
169                                         new_list.Add (item);
170
171                         return new_list;
172                 }
173
174                 static string [] master_infos = Directory.GetFiles (Environment.CurrentDirectory, "*.info");
175
176                 static string FindMasterInfoFile (string name)
177                 {
178                         if (master_infos.Length == 0)
179                                 throw new Exception ("No masterinfo files found in current directory");
180
181                         foreach (string file in master_infos) {
182                                 if (file.EndsWith (name + ".info"))
183                                         return file;
184                         }
185
186                         return null;
187                 }
188
189                 const string xpath_init = "assemblies/assembly/namespaces/namespace[@name='{0}']/classes/class[@name='{1}']";
190
191                 static string GetXPathSearchForType (TypeDefinition type)
192                 {
193                         TypeDefinition parent_type = type;
194                         string xpath = String.Empty;
195                         while (parent_type.DeclaringType != null) {
196                                 xpath = String.Format ("/classes/class[@name='{0}']", parent_type.Name) + xpath;
197                                 parent_type = parent_type.DeclaringType;
198                         }
199                         return String.Format (xpath_init, parent_type.Namespace, parent_type.Name) + xpath;
200                 }
201
202                 static bool IsInternal (MemberReference member, bool master_info)
203                 {
204                         TypeDefinition type = null;
205                         string master_info_file = null;
206
207                         if (member is TypeDefinition) {
208                                 type = member as TypeDefinition;
209                                 if (!master_info)
210                                         return (!type.IsNested && !type.IsPublic) ||
211                                                (type.IsNested && (!type.IsNestedPublic || IsInternal (type.DeclaringType, false)));
212
213                                 master_info_file = FindMasterInfoFile (type.Module.Assembly.Name.Name);
214                                 if (master_info_file == null)
215                                         return IsInternal (member, false);
216
217                                 return !NodeExists (master_info_file, GetXPathSearchForType (type));
218                         }
219
220                         type = member.DeclaringType.Resolve ();
221
222                         if (IsInternal (type, master_info))
223                                 return true;
224
225                         MethodDefinition method = member as MethodDefinition;
226                         FieldDefinition field = member as FieldDefinition;
227
228                         if (field == null && method == null)
229                                 throw new System.NotSupportedException ("Members to scan should be methods or fields");
230
231                         if (!master_info) {
232
233                                 if (method != null)
234                                         return !method.IsPublic;
235
236                                 return !field.IsPublic;
237                         }
238
239                         master_info_file = FindMasterInfoFile (type.Module.Assembly.Name.Name);
240                         if (master_info_file == null)
241                                 return IsInternal (member, false);
242
243                         string xpath_type = GetXPathSearchForType (type);
244                         string name;
245                         if (field != null)
246                                 name = field.Name;
247                         else {
248                                 name = method.ToString ();
249
250                                 //lame, I know...
251                                 name = WackyOutArgs (WackyCommas (name.Substring (name.IndexOf ("::") + 2)
252                                                     .Replace ("/", "+") // nested classes
253                                                     .Replace ('<', '[').Replace ('>', ']'))); //generic params
254                         }
255
256                         if (field != null || !IsPropertyMethod (method))
257                                 return !NodeExists (master_info_file, xpath_type + String.Format ("/*/*[@name='{0}']", name));
258
259                         return !NodeExists (master_info_file, xpath_type + String.Format ("/properties/*/*/*[@name='{0}']", name));
260                 }
261
262                 //at some point I want to get rid of this method and ask cecil's maintainer to spew commas in a uniform way...
263                 static string WackyCommas (string method)
264                 {
265                         string outstring = String.Empty;
266                         bool square_bracket = false;
267                         foreach (char c in method) {
268                                 if (c == '[')
269                                         square_bracket = true;
270                                 else if (c == ']')
271                                         square_bracket = false;
272
273                                 outstring = outstring + c;
274
275                                 if (c == ',' && !square_bracket)
276                                         outstring = outstring + " ";
277                         }
278                         return outstring;
279                 }
280
281                 //ToString() spews & but not 'out' keyword
282                 static string WackyOutArgs (string method)
283                 {
284                         return Regex.Replace (method, @"\w+&", delegate (Match m) { return "out " + m.ToString (); });
285                 }
286
287                 //copied from MarkStep (violating DRY unless I can put this in a better place... Cecil?)
288                 static bool IsPropertyMethod (MethodDefinition md)
289                 {
290                         return (md.SemanticsAttributes & MethodSemanticsAttributes.Getter) != 0 ||
291                                 (md.SemanticsAttributes & MethodSemanticsAttributes.Setter) != 0;
292                 }
293
294                 static Dictionary<string, XPathNavigator> navs = new Dictionary<string, XPathNavigator> ();
295                 static List<FileStream> streams = new List<FileStream> ();
296
297                 static bool NodeExists (string file, string xpath)
298                 {
299                         Console.Write (".");
300                         //Console.WriteLine ("Looking for node {0} in file {1}", xpath, file.Substring (file.LastIndexOf ("/") + 1));
301
302                         XPathNavigator nav = null;
303                         if (!navs.TryGetValue (file, out nav)) {
304                                 FileStream stream = new FileStream (file, FileMode.Open);
305                                 XPathDocument document = new XPathDocument (stream);
306                                 nav = document.CreateNavigator ();
307                                 streams.Add (stream);
308                                 navs [file] = nav;
309                         }
310                         return nav.SelectSingleNode (xpath) != null;
311                 }
312
313                 IList /*List<IAnnotationProvider>*/ ExtractUsedProviders (params IList[] members)
314                 {
315                         IList used = new ArrayList ();
316                         if (members == null || members.Length == 0)
317                                 return used;
318
319                         foreach (IList members_list in members)
320                                 foreach (IMetadataTokenProvider provider in members_list)
321                                         if (Annotations.IsMarked (provider))
322                                                 used.Add (provider);
323
324                         return used;
325                 }
326
327                 class TypeComparer : IComparer <TypeDefinition> {
328
329                         public int Compare (TypeDefinition x, TypeDefinition y)
330                         {
331                                 return string.Compare (x.ToString (), y.ToString ());
332                         }
333
334                 }
335
336         }
337 }