1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 #if FEATURE_DYNAMIC
3 using System.Collections.Generic;
4 using System.Dynamic;
5 using System.Linq.Expressions;
6 using System.Reflection;
7 using System.Runtime.Serialization.Json;
8 
9 namespace System.Json
10 {
11     /// <summary>
12     /// This class provides dynamic behavior support for the JsonValue types.
13     /// </summary>
14     internal class JsonValueDynamicMetaObject : DynamicMetaObject
15     {
16         private static readonly MethodInfo _getValueByIndexMethodInfo = typeof(JsonValue).GetMethod("GetValue", new Type[] { typeof(int) });
17         private static readonly MethodInfo _getValueByKeyMethodInfo = typeof(JsonValue).GetMethod("GetValue", new Type[] { typeof(string) });
18         private static readonly MethodInfo _setValueByIndexMethodInfo = typeof(JsonValue).GetMethod("SetValue", new Type[] { typeof(int), typeof(object) });
19         private static readonly MethodInfo _setValueByKeyMethodInfo = typeof(JsonValue).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) });
20         private static readonly MethodInfo _castValueMethodInfo = typeof(JsonValue).GetMethod("CastValue", new Type[] { typeof(JsonValue) });
21         private static readonly MethodInfo _changeTypeMethodInfo = typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) });
22 
23         /// <summary>
24         /// Class constructor.
25         /// </summary>
26         /// <param name="parameter">The expression representing this <see cref="DynamicMetaObject"/> during the dynamic binding process.</param>
27         /// <param name="value">The runtime value represented by the <see cref="DynamicMetaObject"/>.</param>
JsonValueDynamicMetaObject(Expression parameter, JsonValue value)28         internal JsonValueDynamicMetaObject(Expression parameter, JsonValue value)
29             : base(parameter, BindingRestrictions.Empty, value)
30         {
31         }
32 
33         /// <summary>
34         /// Gets the default binding restrictions for this type.
35         /// </summary>
36         private BindingRestrictions DefaultRestrictions
37         {
38             get { return BindingRestrictions.GetTypeRestriction(Expression, LimitType); }
39         }
40 
41         /// <summary>
42         /// Implements dynamic cast for JsonValue types.
43         /// </summary>
44         /// <param name="binder">An instance of the <see cref="ConvertBinder"/> that represents the details of the dynamic operation.</param>
45         /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
BindConvert(ConvertBinder binder)46         public override DynamicMetaObject BindConvert(ConvertBinder binder)
47         {
48             if (binder == null)
49             {
50                 throw new ArgumentNullException("binder");
51             }
52 
53             Expression expression = Expression;
54 
55             bool implicitCastSupported =
56                 binder.Type.IsAssignableFrom(LimitType) ||
57                 binder.Type == typeof(IEnumerable<KeyValuePair<string, JsonValue>>) ||
58                 binder.Type == typeof(IDynamicMetaObjectProvider) ||
59                 binder.Type == typeof(object);
60 
61             if (!implicitCastSupported)
62             {
63                 if (JsonValue.IsSupportedExplicitCastType(binder.Type))
64                 {
65                     Expression instance = Expression.Convert(Expression, LimitType);
66                     expression = Expression.Call(_castValueMethodInfo.MakeGenericMethod(binder.Type), new Expression[] { instance });
67                 }
68                 else
69                 {
70                     string exceptionMessage = RS.Format(Properties.Resources.CannotCastJsonValue, LimitType.FullName, binder.Type.FullName);
71                     expression = Expression.Throw(Expression.Constant(new InvalidCastException(exceptionMessage)), typeof(object));
72                 }
73             }
74 
75             expression = Expression.Convert(expression, binder.Type);
76 
77             return new DynamicMetaObject(expression, DefaultRestrictions);
78         }
79 
80         /// <summary>
81         /// Implements setter for dynamic indexer by index (JsonArray)
82         /// </summary>
83         /// <param name="binder">An instance of the <see cref="GetIndexBinder"/> that represents the details of the dynamic operation.</param>
84         /// <param name="indexes">An array of <see cref="DynamicMetaObject"/> instances - indexes for the get index operation.</param>
85         /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes)86         public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes)
87         {
88             if (binder == null)
89             {
90                 throw new ArgumentNullException("binder");
91             }
92 
93             if (indexes == null)
94             {
95                 throw new ArgumentNullException("indexes");
96             }
97 
98             Expression indexExpression;
99             if (!JsonValueDynamicMetaObject.TryGetIndexExpression(indexes, out indexExpression))
100             {
101                 return new DynamicMetaObject(indexExpression, DefaultRestrictions);
102             }
103 
104             MethodInfo methodInfo = indexExpression.Type == typeof(string) ? _getValueByKeyMethodInfo : _getValueByIndexMethodInfo;
105             Expression[] args = new Expression[] { indexExpression };
106 
107             return GetMethodMetaObject(methodInfo, args);
108         }
109 
110         /// <summary>
111         /// Implements getter for dynamic indexer by index (JsonArray).
112         /// </summary>
113         /// <param name="binder">An instance of the <see cref="SetIndexBinder"/> that represents the details of the dynamic operation.</param>
114         /// <param name="indexes">An array of <see cref="DynamicMetaObject"/> instances - indexes for the set index operation.</param>
115         /// <param name="value">The <see cref="DynamicMetaObject"/> representing the value for the set index operation.</param>
116         /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value)117         public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value)
118         {
119             if (binder == null)
120             {
121                 throw new ArgumentNullException("binder");
122             }
123 
124             if (indexes == null)
125             {
126                 throw new ArgumentNullException("indexes");
127             }
128 
129             if (value == null)
130             {
131                 throw new ArgumentNullException("value");
132             }
133 
134             Expression indexExpression;
135             if (!JsonValueDynamicMetaObject.TryGetIndexExpression(indexes, out indexExpression))
136             {
137                 return new DynamicMetaObject(indexExpression, DefaultRestrictions);
138             }
139 
140             MethodInfo methodInfo = indexExpression.Type == typeof(string) ? _setValueByKeyMethodInfo : _setValueByIndexMethodInfo;
141             Expression[] args = new Expression[] { indexExpression, Expression.Convert(value.Expression, typeof(object)) };
142 
143             return GetMethodMetaObject(methodInfo, args);
144         }
145 
146         /// <summary>
147         /// Implements getter for dynamic indexer by key (JsonObject).
148         /// </summary>
149         /// <param name="binder">An instance of the <see cref="GetMemberBinder"/> that represents the details of the dynamic operation.</param>
150         /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
BindGetMember(GetMemberBinder binder)151         public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
152         {
153             if (binder == null)
154             {
155                 throw new ArgumentNullException("binder");
156             }
157 
158             PropertyInfo propInfo = LimitType.GetProperty(binder.Name, BindingFlags.Instance | BindingFlags.Public);
159 
160             if (propInfo != null)
161             {
162                 return base.BindGetMember(binder);
163             }
164 
165             Expression[] args = new Expression[] { Expression.Constant(binder.Name) };
166 
167             return GetMethodMetaObject(_getValueByKeyMethodInfo, args);
168         }
169 
170         /// <summary>
171         /// Implements setter for dynamic indexer by key (JsonObject).
172         /// </summary>
173         /// <param name="binder">An instance of the <see cref="SetMemberBinder"/> that represents the details of the dynamic operation.</param>
174         /// <param name="value">The <see cref="DynamicMetaObject"/> representing the value for the set member operation.</param>
175         /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
BindSetMember(SetMemberBinder binder, DynamicMetaObject value)176         public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
177         {
178             if (binder == null)
179             {
180                 throw new ArgumentNullException("binder");
181             }
182 
183             if (value == null)
184             {
185                 throw new ArgumentNullException("value");
186             }
187 
188             Expression[] args = new Expression[] { Expression.Constant(binder.Name), Expression.Convert(value.Expression, typeof(object)) };
189 
190             return GetMethodMetaObject(_setValueByKeyMethodInfo, args);
191         }
192 
193         /// <summary>
194         /// Performs the binding of the dynamic invoke member operation.
195         /// Implemented to support extension methods defined in <see cref="JsonValueExtensions"/> type.
196         /// </summary>
197         /// <param name="binder">An instance of the InvokeMemberBinder that represents the details of the dynamic operation.</param>
198         /// <param name="args">An array of DynamicMetaObject instances - arguments to the invoke member operation.</param>
199         /// <returns>The new DynamicMetaObject representing the result of the binding.</returns>
BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)200         public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
201         {
202             if (binder == null)
203             {
204                 throw new ArgumentNullException("binder");
205             }
206 
207             if (args == null)
208             {
209                 throw new ArgumentNullException("args");
210             }
211 
212             List<Type> argTypeList = new List<Type>();
213 
214             for (int idx = 0; idx < args.Length; idx++)
215             {
216                 argTypeList.Add(args[idx].LimitType);
217             }
218 
219             MethodInfo methodInfo = Value.GetType().GetMethod(binder.Name, argTypeList.ToArray());
220 
221             if (methodInfo == null)
222             {
223                 argTypeList.Insert(0, typeof(JsonValue));
224 
225                 Type[] argTypes = argTypeList.ToArray();
226 
227                 methodInfo = JsonValueDynamicMetaObject.GetExtensionMethod(typeof(JsonValueExtensions), binder.Name, argTypes);
228 
229                 if (methodInfo != null)
230                 {
231                     Expression thisInstance = Expression.Convert(Expression, LimitType);
232                     Expression[] argsExpression = new Expression[argTypes.Length];
233 
234                     argsExpression[0] = thisInstance;
235                     for (int i = 0; i < args.Length; i++)
236                     {
237                         argsExpression[i + 1] = args[i].Expression;
238                     }
239 
240                     Expression callExpression = Expression.Call(methodInfo, argsExpression);
241 
242                     if (methodInfo.ReturnType == typeof(void))
243                     {
244                         callExpression = Expression.Block(callExpression, Expression.Default(binder.ReturnType));
245                     }
246                     else
247                     {
248                         callExpression = Expression.Convert(Expression.Call(methodInfo, argsExpression), binder.ReturnType);
249                     }
250 
251                     return new DynamicMetaObject(callExpression, DefaultRestrictions);
252                 }
253             }
254 
255             return base.BindInvokeMember(binder, args);
256         }
257 
258         /// <summary>
259         /// Returns the enumeration of all dynamic member names.
260         /// </summary>
261         /// <returns>An <see cref="IEnumerable{T}"/> of string reprenseting the dynamic member names.</returns>
GetDynamicMemberNames()262         public override IEnumerable<string> GetDynamicMemberNames()
263         {
264             JsonValue jsonValue = Value as JsonValue;
265 
266             if (jsonValue != null)
267             {
268                 List<string> names = new List<string>();
269 
270                 foreach (KeyValuePair<string, JsonValue> pair in jsonValue)
271                 {
272                     names.Add(pair.Key);
273                 }
274 
275                 return names;
276             }
277 
278             return base.GetDynamicMemberNames();
279         }
280 
281         /// <summary>
282         /// Gets a <see cref="MethodInfo"/> instance for the specified method name in the specified type.
283         /// </summary>
284         /// <param name="extensionProviderType">The extension provider type.</param>
285         /// <param name="methodName">The name of the method to get the info for.</param>
286         /// <param name="argTypes">The types of the method arguments.</param>
287         /// <returns>A <see cref="MethodInfo"/>instance or null if the method cannot be resolved.</returns>
GetExtensionMethod(Type extensionProviderType, string methodName, Type[] argTypes)288         private static MethodInfo GetExtensionMethod(Type extensionProviderType, string methodName, Type[] argTypes)
289         {
290             MethodInfo methodInfo = null;
291             MethodInfo[] methods = extensionProviderType.GetMethods();
292 
293             foreach (MethodInfo info in methods)
294             {
295                 if (info.Name == methodName)
296                 {
297                     methodInfo = info;
298 
299                     if (!info.IsGenericMethodDefinition)
300                     {
301                         bool paramsMatch = true;
302                         ParameterInfo[] args = methodInfo.GetParameters();
303 
304                         if (args.Length == argTypes.Length)
305                         {
306                             for (int idx = 0; idx < args.Length; idx++)
307                             {
308                                 if (!args[idx].ParameterType.IsAssignableFrom(argTypes[idx]))
309                                 {
310                                     paramsMatch = false;
311                                     break;
312                                 }
313                             }
314 
315                             if (paramsMatch)
316                             {
317                                 break;
318                             }
319                         }
320                     }
321                 }
322             }
323 
324             return methodInfo;
325         }
326 
327         /// <summary>
328         /// Attempts to get an expression for an index parameter.
329         /// </summary>
330         /// <param name="indexes">The operation indexes parameter.</param>
331         /// <param name="expression">A <see cref="Expression"/> to be initialized to the index expression if the operation is successful, otherwise an error expression.</param>
332         /// <returns>true the operation is successful, false otherwise.</returns>
TryGetIndexExpression(DynamicMetaObject[] indexes, out Expression expression)333         private static bool TryGetIndexExpression(DynamicMetaObject[] indexes, out Expression expression)
334         {
335             if (indexes.Length == 1 && indexes[0] != null && indexes[0].Value != null)
336             {
337                 DynamicMetaObject index = indexes[0];
338                 Type indexType = indexes[0].Value.GetType();
339 
340                 switch (Type.GetTypeCode(indexType))
341                 {
342                     case TypeCode.Char:
343                     case TypeCode.Int16:
344                     case TypeCode.UInt16:
345                     case TypeCode.Byte:
346                     case TypeCode.SByte:
347                         Expression argExp = Expression.Convert(index.Expression, typeof(object));
348                         Expression typeExp = Expression.Constant(typeof(int));
349                         expression = Expression.Convert(Expression.Call(_changeTypeMethodInfo, new Expression[] { argExp, typeExp }), typeof(int));
350                         return true;
351 
352                     case TypeCode.Int32:
353                     case TypeCode.String:
354                         expression = index.Expression;
355                         return true;
356                 }
357 
358                 expression = Expression.Throw(Expression.Constant(new ArgumentException(RS.Format(Properties.Resources.InvalidIndexType, indexType))), typeof(object));
359                 return false;
360             }
361 
362             expression = Expression.Throw(Expression.Constant(new ArgumentException(Properties.Resources.NonSingleNonNullIndexNotSupported)), typeof(object));
363             return false;
364         }
365 
366         /// <summary>
367         /// Gets a <see cref="DynamicMetaObject"/> for a method call.
368         /// </summary>
369         /// <param name="methodInfo">Info for the method to be performed.</param>
370         /// <param name="args">expression array representing the method arguments</param>
371         /// <returns>A meta object for the method call.</returns>
GetMethodMetaObject(MethodInfo methodInfo, Expression[] args)372         private DynamicMetaObject GetMethodMetaObject(MethodInfo methodInfo, Expression[] args)
373         {
374             Expression instance = Expression.Convert(Expression, LimitType);
375             Expression methodCall = Expression.Call(instance, methodInfo, args);
376             BindingRestrictions restrictions = DefaultRestrictions;
377 
378             DynamicMetaObject metaObj = new DynamicMetaObject(methodCall, restrictions);
379 
380             return metaObj;
381         }
382     }
383 }
384 #endif
385