// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. #if FEATURE_DYNAMIC using System.Collections.Generic; using System.Dynamic; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization.Json; namespace System.Json { /// /// This class provides dynamic behavior support for the JsonValue types. /// internal class JsonValueDynamicMetaObject : DynamicMetaObject { private static readonly MethodInfo _getValueByIndexMethodInfo = typeof(JsonValue).GetMethod("GetValue", new Type[] { typeof(int) }); private static readonly MethodInfo _getValueByKeyMethodInfo = typeof(JsonValue).GetMethod("GetValue", new Type[] { typeof(string) }); private static readonly MethodInfo _setValueByIndexMethodInfo = typeof(JsonValue).GetMethod("SetValue", new Type[] { typeof(int), typeof(object) }); private static readonly MethodInfo _setValueByKeyMethodInfo = typeof(JsonValue).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) }); private static readonly MethodInfo _castValueMethodInfo = typeof(JsonValue).GetMethod("CastValue", new Type[] { typeof(JsonValue) }); private static readonly MethodInfo _changeTypeMethodInfo = typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }); /// /// Class constructor. /// /// The expression representing this during the dynamic binding process. /// The runtime value represented by the . internal JsonValueDynamicMetaObject(Expression parameter, JsonValue value) : base(parameter, BindingRestrictions.Empty, value) { } /// /// Gets the default binding restrictions for this type. /// private BindingRestrictions DefaultRestrictions { get { return BindingRestrictions.GetTypeRestriction(Expression, LimitType); } } /// /// Implements dynamic cast for JsonValue types. /// /// An instance of the that represents the details of the dynamic operation. /// The new representing the result of the binding. public override DynamicMetaObject BindConvert(ConvertBinder binder) { if (binder == null) { throw new ArgumentNullException("binder"); } Expression expression = Expression; bool implicitCastSupported = binder.Type.IsAssignableFrom(LimitType) || binder.Type == typeof(IEnumerable>) || binder.Type == typeof(IDynamicMetaObjectProvider) || binder.Type == typeof(object); if (!implicitCastSupported) { if (JsonValue.IsSupportedExplicitCastType(binder.Type)) { Expression instance = Expression.Convert(Expression, LimitType); expression = Expression.Call(_castValueMethodInfo.MakeGenericMethod(binder.Type), new Expression[] { instance }); } else { string exceptionMessage = RS.Format(Properties.Resources.CannotCastJsonValue, LimitType.FullName, binder.Type.FullName); expression = Expression.Throw(Expression.Constant(new InvalidCastException(exceptionMessage)), typeof(object)); } } expression = Expression.Convert(expression, binder.Type); return new DynamicMetaObject(expression, DefaultRestrictions); } /// /// Implements setter for dynamic indexer by index (JsonArray) /// /// An instance of the that represents the details of the dynamic operation. /// An array of instances - indexes for the get index operation. /// The new representing the result of the binding. public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { if (binder == null) { throw new ArgumentNullException("binder"); } if (indexes == null) { throw new ArgumentNullException("indexes"); } Expression indexExpression; if (!JsonValueDynamicMetaObject.TryGetIndexExpression(indexes, out indexExpression)) { return new DynamicMetaObject(indexExpression, DefaultRestrictions); } MethodInfo methodInfo = indexExpression.Type == typeof(string) ? _getValueByKeyMethodInfo : _getValueByIndexMethodInfo; Expression[] args = new Expression[] { indexExpression }; return GetMethodMetaObject(methodInfo, args); } /// /// Implements getter for dynamic indexer by index (JsonArray). /// /// An instance of the that represents the details of the dynamic operation. /// An array of instances - indexes for the set index operation. /// The representing the value for the set index operation. /// The new representing the result of the binding. public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { if (binder == null) { throw new ArgumentNullException("binder"); } if (indexes == null) { throw new ArgumentNullException("indexes"); } if (value == null) { throw new ArgumentNullException("value"); } Expression indexExpression; if (!JsonValueDynamicMetaObject.TryGetIndexExpression(indexes, out indexExpression)) { return new DynamicMetaObject(indexExpression, DefaultRestrictions); } MethodInfo methodInfo = indexExpression.Type == typeof(string) ? _setValueByKeyMethodInfo : _setValueByIndexMethodInfo; Expression[] args = new Expression[] { indexExpression, Expression.Convert(value.Expression, typeof(object)) }; return GetMethodMetaObject(methodInfo, args); } /// /// Implements getter for dynamic indexer by key (JsonObject). /// /// An instance of the that represents the details of the dynamic operation. /// The new representing the result of the binding. public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { if (binder == null) { throw new ArgumentNullException("binder"); } PropertyInfo propInfo = LimitType.GetProperty(binder.Name, BindingFlags.Instance | BindingFlags.Public); if (propInfo != null) { return base.BindGetMember(binder); } Expression[] args = new Expression[] { Expression.Constant(binder.Name) }; return GetMethodMetaObject(_getValueByKeyMethodInfo, args); } /// /// Implements setter for dynamic indexer by key (JsonObject). /// /// An instance of the that represents the details of the dynamic operation. /// The representing the value for the set member operation. /// The new representing the result of the binding. public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { if (binder == null) { throw new ArgumentNullException("binder"); } if (value == null) { throw new ArgumentNullException("value"); } Expression[] args = new Expression[] { Expression.Constant(binder.Name), Expression.Convert(value.Expression, typeof(object)) }; return GetMethodMetaObject(_setValueByKeyMethodInfo, args); } /// /// Performs the binding of the dynamic invoke member operation. /// Implemented to support extension methods defined in type. /// /// An instance of the InvokeMemberBinder that represents the details of the dynamic operation. /// An array of DynamicMetaObject instances - arguments to the invoke member operation. /// The new DynamicMetaObject representing the result of the binding. public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { if (binder == null) { throw new ArgumentNullException("binder"); } if (args == null) { throw new ArgumentNullException("args"); } List argTypeList = new List(); for (int idx = 0; idx < args.Length; idx++) { argTypeList.Add(args[idx].LimitType); } MethodInfo methodInfo = Value.GetType().GetMethod(binder.Name, argTypeList.ToArray()); if (methodInfo == null) { argTypeList.Insert(0, typeof(JsonValue)); Type[] argTypes = argTypeList.ToArray(); methodInfo = JsonValueDynamicMetaObject.GetExtensionMethod(typeof(JsonValueExtensions), binder.Name, argTypes); if (methodInfo != null) { Expression thisInstance = Expression.Convert(Expression, LimitType); Expression[] argsExpression = new Expression[argTypes.Length]; argsExpression[0] = thisInstance; for (int i = 0; i < args.Length; i++) { argsExpression[i + 1] = args[i].Expression; } Expression callExpression = Expression.Call(methodInfo, argsExpression); if (methodInfo.ReturnType == typeof(void)) { callExpression = Expression.Block(callExpression, Expression.Default(binder.ReturnType)); } else { callExpression = Expression.Convert(Expression.Call(methodInfo, argsExpression), binder.ReturnType); } return new DynamicMetaObject(callExpression, DefaultRestrictions); } } return base.BindInvokeMember(binder, args); } /// /// Returns the enumeration of all dynamic member names. /// /// An of string reprenseting the dynamic member names. public override IEnumerable GetDynamicMemberNames() { JsonValue jsonValue = Value as JsonValue; if (jsonValue != null) { List names = new List(); foreach (KeyValuePair pair in jsonValue) { names.Add(pair.Key); } return names; } return base.GetDynamicMemberNames(); } /// /// Gets a instance for the specified method name in the specified type. /// /// The extension provider type. /// The name of the method to get the info for. /// The types of the method arguments. /// A instance or null if the method cannot be resolved. private static MethodInfo GetExtensionMethod(Type extensionProviderType, string methodName, Type[] argTypes) { MethodInfo methodInfo = null; MethodInfo[] methods = extensionProviderType.GetMethods(); foreach (MethodInfo info in methods) { if (info.Name == methodName) { methodInfo = info; if (!info.IsGenericMethodDefinition) { bool paramsMatch = true; ParameterInfo[] args = methodInfo.GetParameters(); if (args.Length == argTypes.Length) { for (int idx = 0; idx < args.Length; idx++) { if (!args[idx].ParameterType.IsAssignableFrom(argTypes[idx])) { paramsMatch = false; break; } } if (paramsMatch) { break; } } } } } return methodInfo; } /// /// Attempts to get an expression for an index parameter. /// /// The operation indexes parameter. /// A to be initialized to the index expression if the operation is successful, otherwise an error expression. /// true the operation is successful, false otherwise. private static bool TryGetIndexExpression(DynamicMetaObject[] indexes, out Expression expression) { if (indexes.Length == 1 && indexes[0] != null && indexes[0].Value != null) { DynamicMetaObject index = indexes[0]; Type indexType = indexes[0].Value.GetType(); switch (Type.GetTypeCode(indexType)) { case TypeCode.Char: case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Byte: case TypeCode.SByte: Expression argExp = Expression.Convert(index.Expression, typeof(object)); Expression typeExp = Expression.Constant(typeof(int)); expression = Expression.Convert(Expression.Call(_changeTypeMethodInfo, new Expression[] { argExp, typeExp }), typeof(int)); return true; case TypeCode.Int32: case TypeCode.String: expression = index.Expression; return true; } expression = Expression.Throw(Expression.Constant(new ArgumentException(RS.Format(Properties.Resources.InvalidIndexType, indexType))), typeof(object)); return false; } expression = Expression.Throw(Expression.Constant(new ArgumentException(Properties.Resources.NonSingleNonNullIndexNotSupported)), typeof(object)); return false; } /// /// Gets a for a method call. /// /// Info for the method to be performed. /// expression array representing the method arguments /// A meta object for the method call. private DynamicMetaObject GetMethodMetaObject(MethodInfo methodInfo, Expression[] args) { Expression instance = Expression.Convert(Expression, LimitType); Expression methodCall = Expression.Call(instance, methodInfo, args); BindingRestrictions restrictions = DefaultRestrictions; DynamicMetaObject metaObj = new DynamicMetaObject(methodCall, restrictions); return metaObj; } } } #endif