2 // MemberInvocationReference.cs
5 // Marek Safar <marek.safar@gmail.com>
7 // Copyright (C) 2014 Xamarin Inc (http://www.xamarin.com)
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;
32 using System.Reflection;
33 using System.Globalization;
34 using Microsoft.Build.Framework;
37 namespace Microsoft.Build.BuildEngine
39 class MemberInvocationReference : IReference
44 static readonly char[] ArgumentTrimChars = new char[] { '\"', '\'', '`' };
45 static readonly object ConversionFailed = new object ();
47 public MemberInvocationReference (Type type, string name)
53 public List<string> Arguments { get; set; }
55 public IReference Instance { get; set; }
57 public string ConvertToString (Project project, ExpressionOptions options)
59 return ConvertResult (Invoke (project, options));
62 object Invoke (Project project, ExpressionOptions options)
64 var flags = BindingFlags.IgnoreCase | BindingFlags.Public;
66 string member_name = name;
68 if (Instance == null) {
70 if (string.Equals (member_name, "new", StringComparison.OrdinalIgnoreCase)) {
71 member_name = ConstructorInfo.ConstructorName;
72 flags |= BindingFlags.CreateInstance | BindingFlags.Instance;
74 flags |= BindingFlags.Static;
77 var mir = Instance as MemberInvocationReference;
79 target = mir.Invoke (project, options);
81 throw new NotImplementedException ("Instance method on null value");
84 type = target.GetType ();
86 target = Instance.ConvertToString (project, options);
87 type = typeof (string);
90 flags |= BindingFlags.Instance;
94 if (Arguments == null) {
95 flags |= BindingFlags.GetProperty;
98 flags |= BindingFlags.InvokeMethod;
99 ExpandArguments (project, options);
100 args = PrepareMethodArguments (member_name, flags);
102 throw new InvalidProjectFileException (string.Format ("Method '{0}({1})' arguments cannot be evaluated'", name, string.Join (", ", Arguments.ToArray ())));
107 value = type.InvokeMember (member_name, flags, null, target, args, CultureInfo.InvariantCulture);
108 } catch (MissingFieldException) {
110 // It can be field/constant instead of a property
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);
124 void ExpandArguments (Project project, ExpressionOptions options)
126 for (int i = 0; i < Arguments.Count; ++i) {
127 string arg = Arguments [i].Trim ();
128 if (string.Equals (arg, "null", StringComparison.OrdinalIgnoreCase)) {
131 arg = Expression.ParseAs<string> (arg, ParseOptions.None,
134 arg = arg.Trim (ArgumentTrimChars);
141 object[] PrepareMethodArguments (string name, BindingFlags flags)
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)
151 if (parameters.Length == 0)
152 return new object [0];
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) {
162 if (cand_args == null)
163 cand_args = new object[parameters.Length];
165 cand_args [i] = target;
168 if (cand_args == null)
177 if (BetterCandidate (best, parameters) > 1) {
186 static object ConvertArgument (object value, ParameterInfo target)
188 var ptype = target.ParameterType;
190 var s = value as string;
192 return ConvertToEnum (s, ptype);
193 } else if (ptype == typeof (char[])) {
194 var s = value as string;
196 return s.ToCharArray ();
200 return Convert.ChangeType (value, ptype, CultureInfo.InvariantCulture);
202 return ConversionFailed;
206 static object ConvertToEnum (string s, Type type)
208 var dot = s.IndexOf ('.');
210 return ConversionFailed;
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);
220 return Enum.Parse (type, s);
222 return ConversionFailed;
226 static string ConvertResult (object value)
229 return (string)value;
231 var e = value as IEnumerable;
233 var sb = new StringBuilder ();
234 foreach (var entry in e) {
238 sb.Append (ConvertResult (entry));
241 return sb.ToString ();
244 return value == null ? "" : value.ToString ();
248 // Returns better candidate for untyped string values. We can really do only
249 // preference for string over any other types
253 // 0: neither is better
255 static int BetterCandidate (ParameterInfo[] a, ParameterInfo[] b)
258 for (int i = 0; i < a.Length; ++i) {
259 var atype = a [i].ParameterType;
260 var btype = b [i].ParameterType;
262 if (atype == typeof (string) && btype != atype) {
268 if (btype == typeof (string) && btype != atype) {
279 public ITaskItem[] ConvertToITaskItemArray (Project project, ExpressionOptions options)
281 throw new NotImplementedException ();