6430992fba9f81fc08bedd6bf33e0a6c6e7a00ed
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / MemberInvocationReference.cs
1 //
2 // MemberInvocationReference.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2014 Xamarin Inc (http://www.xamarin.com)
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 using System.Reflection;
33 using System.Globalization;
34 using Microsoft.Build.Framework;
35 using System.Text;
36
37 namespace Microsoft.Build.BuildEngine
38 {
39         class MemberInvocationReference : IReference
40         {
41                 Type type;
42                 readonly string name;
43
44                 static readonly char[] ArgumentTrimChars = new char[] { '\"', '\'', '`' };
45                 static readonly object ConversionFailed = new object ();
46
47                 public MemberInvocationReference (Type type, string name)
48                 {
49                         this.type = type;
50                         this.name = name;
51                 }
52
53                 public List<string> Arguments { get; set; }
54
55                 public IReference Instance { get; set; }
56
57                 public string ConvertToString (Project project, ExpressionOptions options)
58                 {
59                         return ConvertResult (Invoke (project, options));
60                 }
61
62                 object Invoke (Project project, ExpressionOptions options)
63                 {
64                         var flags = BindingFlags.IgnoreCase | BindingFlags.Public;
65                         object target;
66                         string member_name = name;
67
68                         if (Instance == null) {
69                                 target = null;
70                                 if (string.Equals (member_name, "new", StringComparison.OrdinalIgnoreCase)) {
71                                         member_name = ConstructorInfo.ConstructorName;
72                                         flags |= BindingFlags.CreateInstance | BindingFlags.Instance;
73                                 } else {
74                                         flags |= BindingFlags.Static;
75                                 }
76                         } else {
77                                 var mir = Instance as MemberInvocationReference;
78                                 if (mir != null) {
79                                         target = mir.Invoke (project, options);
80                                         if (target == null) {
81                                                 throw new NotImplementedException ("Instance method on null value");
82                                         }
83
84                                         type = target.GetType ();
85                                 } else {
86                                         target = Instance.ConvertToString (project, options);
87                                         type = typeof (string);
88                                 }
89
90                                 flags |= BindingFlags.Instance;
91                         }
92
93                         object[] args;
94                         if (Arguments == null) {
95                                 flags |= BindingFlags.GetProperty;
96                                 args = null;
97                         } else {
98                                 flags |= BindingFlags.InvokeMethod;
99                                 ExpandArguments (project, options);
100                                 args = PrepareMethodArguments (member_name, flags);
101                                 if (args == null)
102                                         throw new InvalidProjectFileException (string.Format ("Method '{0}({1})' arguments cannot be evaluated'", name, string.Join (", ", Arguments.ToArray ())));
103                         }
104
105                         object value;
106                         try {
107                                 value = type.InvokeMember (member_name, flags, null, target, args, CultureInfo.InvariantCulture);
108                         } catch (MissingFieldException) {
109                                 //
110                                 // It can be field/constant instead of a property
111                                 //
112                                 if (args == null && Instance == null) {
113                                         flags &= ~BindingFlags.GetProperty;
114                                         flags |= BindingFlags.GetField;
115                                         value = type.InvokeMember (member_name, flags, null, null, null, CultureInfo.InvariantCulture);
116                                 } else {
117                                         throw;
118                                 }
119                         }
120
121                         return value;
122                 }
123
124                 void ExpandArguments (Project project, ExpressionOptions options)
125                 {
126                         for (int i = 0; i < Arguments.Count; ++i) {
127                                 string arg = Arguments [i].Trim ();
128                                 if (string.Equals (arg, "null", StringComparison.OrdinalIgnoreCase)) {
129                                         arg = null;
130                                 } else {
131                                         arg = Expression.ParseAs<string> (arg, ParseOptions.None,
132                                                 project, options);
133
134                                         arg = arg.Trim (ArgumentTrimChars);
135                                 }
136
137                                 Arguments [i] = arg;
138                         }
139                 }
140
141                 object[] PrepareMethodArguments (string name, BindingFlags flags)
142                 {
143                         var candidates = type.GetMember (name, MemberTypes.Method | MemberTypes.Constructor, flags);
144                         object[] args = null;
145                         ParameterInfo[] best = null;
146                         foreach (MethodBase candidate in candidates) {
147                                 var parameters = candidate.GetParameters ();
148                                 if (parameters.Length != Arguments.Count)
149                                         continue;
150
151                                 if (parameters.Length == 0)
152                                         return new object [0];
153
154                                 object[] cand_args = null;
155                                 for (int i = 0; i < parameters.Length; ++i) {
156                                         var target = ConvertArgument (Arguments [i], parameters [i]);
157                                         if (target == ConversionFailed) {
158                                                 cand_args = null;
159                                                 break;
160                                         }
161
162                                         if (cand_args == null)
163                                                 cand_args = new object[parameters.Length];
164
165                                         cand_args [i] = target;
166                                 }
167
168                                 if (cand_args == null)
169                                         continue;
170
171                                 if (args == null) {
172                                         args = cand_args;
173                                         best = parameters;
174                                         continue;
175                                 }
176
177                                 if (BetterCandidate (best, parameters) > 1) {
178                                         args = cand_args;
179                                         best = parameters;
180                                 }
181                         }
182
183                         return args;
184                 }
185
186                 static object ConvertArgument (object value, ParameterInfo target)
187                 {
188                         var ptype = target.ParameterType;
189                         if (ptype.IsEnum) {
190                                 var s = value as string;
191                                 if (s != null)
192                                         return ConvertToEnum (s, ptype);
193                         } else if (ptype == typeof (char[])) {
194                                 var s = value as string;
195                                 if (s != null)
196                                         return s.ToCharArray ();
197                         }
198
199                         try {
200                                 return Convert.ChangeType (value, ptype, CultureInfo.InvariantCulture);
201                         } catch {
202                                 return ConversionFailed;
203                         }
204                 }
205
206                 static object ConvertToEnum (string s, Type type)
207                 {
208                         var dot = s.IndexOf ('.');
209                         if (dot < 0)
210                                 return ConversionFailed;
211
212                         var fn = type.FullName + ".";
213                         if (s.StartsWith (fn, StringComparison.Ordinal)) {
214                                 s = s.Substring (fn.Length);
215                         } else if (s.StartsWith (type.Name, StringComparison.Ordinal) && s [type.Name.Length] == '.') {
216                                 s = s.Substring (type.Name.Length + 1);
217                         }
218
219                         try {
220                                 return Enum.Parse (type, s);
221                         } catch {
222                                 return ConversionFailed;
223                         }
224                 }
225
226                 static string ConvertResult (object value)
227                 {
228                         if (value is string)
229                                 return (string)value;
230
231                         var e = value as IEnumerable;
232                         if (e != null) {
233                                 var sb = new StringBuilder ();
234                                 foreach (var entry in e) {
235                                         if (sb.Length > 0)
236                                                 sb.Append (";");
237
238                                         sb.Append (ConvertResult (entry));
239                                 }
240
241                                 return sb.ToString ();
242                         }
243
244                         return value == null ? "" : value.ToString ();
245                 }
246
247                 //
248                 // Returns better candidate for untyped string values. We can really do only
249                 // preference for string over any other types
250                 //
251                 // 1: a is better
252                 // 2: b is better
253                 // 0: neither is better
254                 //
255                 static int BetterCandidate (ParameterInfo[] a, ParameterInfo[] b)
256                 {
257                         int res = 0;
258                         for (int i = 0; i < a.Length; ++i) {
259                                 var atype = a [i].ParameterType;
260                                 var btype = b [i].ParameterType;
261
262                                 if (atype == typeof (string) && btype != atype) {
263                                         if (res < 2)
264                                                 res = 1;
265                                         continue;
266                                 }
267
268                                 if (btype == typeof (string) && btype != atype) {
269                                         if (res != 1)
270                                                 res = 2;
271
272                                         continue;
273                                 }
274                         }
275
276                         return res;
277                 }
278
279                 public ITaskItem[] ConvertToITaskItemArray (Project project, ExpressionOptions options)
280                 {
281                         throw new NotImplementedException ();
282                 }
283         }
284 }