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