2 // MoonlightA11yDescriptorGenerator.cs
5 // Andrés G. Aragoneses (aaragoneses@novell.com)
7 // (C) 2009 Novell, Inc.
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
30 using System
.Collections
;
31 using System
.Collections
.Generic
;
34 using System
.Text
.RegularExpressions
;
38 using System
.Xml
.XPath
;
41 using Mono
.Linker
.Steps
;
45 namespace Mono
.Tuner
{
47 public class MoonlightA11yDescriptorGenerator
: BaseStep
{
49 XmlTextWriter writer
= null;
50 protected override void ProcessAssembly (AssemblyDefinition assembly
)
52 if (assembly
.Name
.Name
== "MoonAtkBridge" || assembly
.Name
.Name
== "System.Windows" ||
53 assembly
.Name
.Name
.Contains ("Dummy"))
57 if (!Directory
.Exists (Context
.OutputDirectory
))
58 Directory
.CreateDirectory (Context
.OutputDirectory
);
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...");
68 writer
= new XmlTextWriter (xml_file
, System
.Text
.Encoding
.UTF8
);
69 writer
.Formatting
= Formatting
.Indented
;
70 writer
.WriteStartElement("linker");
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
);
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
);
84 foreach (IAnnotationProvider member
in members
) {
85 MethodDefinition method
= member
as MethodDefinition
;
87 writer
.WriteStartElement("method");
88 writer
.WriteAttributeString ("signature",
89 method
.ReturnType
.ReturnType
.FullName
+ " " +
90 method
.Name
+ GetMethodParams (method
));
91 writer
.WriteEndElement ();
95 FieldDefinition field
= member
as FieldDefinition
;
97 writer
.WriteStartElement("field");
98 writer
.WriteAttributeString ("signature", field
.DeclaringType
.FullName
+ " " + field
.Name
);
99 writer
.WriteEndElement ();
102 writer
.WriteEndElement ();
106 writer
.WriteEndElement ();
107 Console
.WriteLine ();
112 protected override void EndProcess ()
114 Console
.WriteLine ();
116 foreach (FileStream stream
in streams
)
119 if (writer
!= null) {
120 writer
.WriteEndElement ();
126 //this is almost the ToString method of MethodDefinition...
127 private string GetMethodParams (MethodDefinition method
)
129 string @params = "(";
130 if (method
.HasParameters
) {
131 for (int i
= 0; i
< method
.Parameters
.Count
; i
++) {
135 @params += method
.Parameters
[i
].ParameterType
.FullName
;
142 SortedDictionary
<TypeDefinition
, IList
> /*,List<IAnnotationProvider>>*/ ScanAssembly (AssemblyDefinition assembly
)
144 if (Annotations
.GetAction (assembly
) != AssemblyAction
.Link
)
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 ()));
159 static IList
ScanType (TypeDefinition type
)
161 return ExtractUsedProviders (type
.Methods
, type
.Constructors
, type
.Fields
);
164 static IList
FilterPublicMembers (IList members
)
166 IList new_list
= new ArrayList ();
167 foreach (MemberReference item
in members
)
168 if (IsInternal (item
, true))
174 static string [] master_infos
= Directory
.GetFiles (Environment
.CurrentDirectory
, "*.info");
176 static string FindMasterInfoFile (string name
)
178 if (master_infos
.Length
== 0)
179 throw new Exception ("No masterinfo files found in current directory");
181 foreach (string file
in master_infos
) {
182 if (file
.EndsWith (name
+ ".info"))
189 const string xpath_init
= "assemblies/assembly/namespaces/namespace[@name='{0}']/classes/class[@name='{1}']";
191 static string GetXPathSearchForType (TypeDefinition type
)
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
;
199 return String
.Format (xpath_init
, parent_type
.Namespace
, parent_type
.Name
) + xpath
;
202 static bool IsInternal (MemberReference member
, bool master_info
)
204 TypeDefinition type
= null;
205 string master_info_file
= null;
207 if (member
is TypeDefinition
) {
208 type
= member
as TypeDefinition
;
210 return (!type
.IsNested
&& !type
.IsPublic
) ||
211 (type
.IsNested
&& (!type
.IsNestedPublic
|| IsInternal (type
.DeclaringType
, false)));
213 master_info_file
= FindMasterInfoFile (type
.Module
.Assembly
.Name
.Name
);
214 if (master_info_file
== null)
215 return IsInternal (member
, false);
217 return !NodeExists (master_info_file
, GetXPathSearchForType (type
));
220 type
= member
.DeclaringType
.Resolve ();
222 if (IsInternal (type
, master_info
))
225 MethodDefinition method
= member
as MethodDefinition
;
226 FieldDefinition field
= member
as FieldDefinition
;
228 if (field
== null && method
== null)
229 throw new System
.NotSupportedException ("Members to scan should be methods or fields");
234 return !method
.IsPublic
;
236 return !field
.IsPublic
;
239 master_info_file
= FindMasterInfoFile (type
.Module
.Assembly
.Name
.Name
);
240 if (master_info_file
== null)
241 return IsInternal (member
, false);
243 string xpath_type
= GetXPathSearchForType (type
);
248 name
= method
.ToString ();
251 name
= WackyOutArgs (WackyCommas (name
.Substring (name
.IndexOf ("::") + 2)
252 .Replace ("/", "+") // nested classes
253 .Replace ('<', '[').Replace ('>', ']'))); //generic params
256 if (field
!= null || !IsPropertyMethod (method
))
257 return !NodeExists (master_info_file
, xpath_type
+ String
.Format ("/*/*[@name='{0}']", name
));
259 return !NodeExists (master_info_file
, xpath_type
+ String
.Format ("/properties/*/*/*[@name='{0}']", name
));
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
)
265 string outstring
= String
.Empty
;
266 bool square_bracket
= false;
267 foreach (char c
in method
) {
269 square_bracket
= true;
271 square_bracket
= false;
273 outstring
= outstring
+ c
;
275 if (c
== ',' && !square_bracket
)
276 outstring
= outstring
+ " ";
281 //ToString() spews & but not 'out' keyword
282 static string WackyOutArgs (string method
)
284 return Regex
.Replace (method
, @"\w+&", delegate (Match m
) { return "out " + m.ToString (); }
);
287 //copied from MarkStep (violating DRY unless I can put this in a better place... Cecil?)
288 static bool IsPropertyMethod (MethodDefinition md
)
290 return (md
.SemanticsAttributes
& MethodSemanticsAttributes
.Getter
) != 0 ||
291 (md
.SemanticsAttributes
& MethodSemanticsAttributes
.Setter
) != 0;
294 static Dictionary
<string, XPathNavigator
> navs
= new Dictionary
<string, XPathNavigator
> ();
295 static List
<FileStream
> streams
= new List
<FileStream
> ();
297 static bool NodeExists (string file
, string xpath
)
300 //Console.WriteLine ("Looking for node {0} in file {1}", xpath, file.Substring (file.LastIndexOf ("/") + 1));
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
);
310 return nav
.SelectSingleNode (xpath
) != null;
313 static IList
/*List<IAnnotationProvider>*/ ExtractUsedProviders (params IList
[] members
)
315 IList used
= new ArrayList ();
316 if (members
== null || members
.Length
== 0)
319 foreach (IList members_list
in members
)
320 foreach (IAnnotationProvider provider
in members_list
)
321 if (Annotations
.IsMarked (provider
))
327 class TypeComparer
: IComparer
<TypeDefinition
> {
329 public int Compare (TypeDefinition x
, TypeDefinition y
)
331 return string.Compare (x
.ToString (), y
.ToString ());