context: #region License
  // Copyright (c) 2007 James Newton-King
  //
  // Permission is hereby granted, free of charge, to any person
  // obtaining a copy of this software and associated documentation
  // files (the "Software"), to deal in the Software without
  // restriction, including without limitation the rights to use,
  // copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the
  // Software is furnished to do so, subject to the following
  // conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  // OTHER DEALINGS IN THE SOFTWARE.
  #endregion
  
  #if !(NET20 || NET35)
  
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System;
  using System.Linq.Expressions;
  using System.Reflection;
  using Newtonsoft.Json.Serialization;
  
  namespace Newtonsoft.Json.Utilities
  {
      internal class ExpressionReflectionDelegateFactory : ReflectionDelegateFactory
      {
          private static readonly ExpressionReflectionDelegateFactory _instance = new ExpressionReflectionDelegateFactory();
  
          internal static ReflectionDelegateFactory Instance
          {
              get { return _instance; }
          }
  
          public override ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
          {
              ValidationUtils.ArgumentNotNull(method, nameof(method));
  
              Type type = typeof(object);
  
              ParameterExpression argsParameterExpression = Expression.Parameter(typeof(object[]), "args");
  
              Expression callExpression = BuildMethodCall(method, type, null, argsParameterExpression);
  
              LambdaExpression lambdaExpression = Expression.Lambda(typeof(ObjectConstructor<object>), callExpression, argsParameterExpression);
  
              ObjectConstructor<object> compiled = (ObjectConstructor<object>)lambdaExpression.Compile();
              return compiled;
          }
  
          public override MethodCall<T, object> CreateMethodCall<T>(MethodBase method)
          {
              ValidationUtils.ArgumentNotNull(method, nameof(method));
  
              Type type = typeof(object);
  
              ParameterExpression targetParameterExpression = Expression.Parameter(type, "target");
              ParameterExpression argsParameterExpression = Expression.Parameter(typeof(object[]), "args");
  
              Expression callExpression = BuildMethodCall(method, type, targetParameterExpression, argsParameterExpression);
  
              LambdaExpression lambdaExpression = Expression.Lambda(typeof(MethodCall<T, object>), callExpression, targetParameterExpression, argsParameterExpression);
  
              MethodCall<T, object> compiled = (MethodCall<T, object>)lambdaExpression.Compile();
              return compiled;
          }
  
          private class ByRefParameter
          {
              public Expression Value;
              public ParameterExpression Variable;
              public bool IsOut;
          }
  
          private Expression BuildMethodCall(MethodBase method, Type type, ParameterExpression targetParameterExpression, ParameterExpression argsParameterExpression)
          {
              ParameterInfo[] parametersInfo = method.GetParameters();
  
              Expression[] argsExpression;
              IList<ByRefParameter> refParameterMap;
              if (parametersInfo.Length == 0)
              {
                  argsExpression = CollectionUtils.ArrayEmpty<Expression>();
                  refParameterMap = CollectionUtils.ArrayEmpty<ByRefParameter>();
              }
              else
              {
                  argsExpression = new Expression[parametersInfo.Length];
                  refParameterMap = new List<ByRefParameter>();
  
                  for (int i = 0; i < parametersInfo.Length; i++)
                  {
                      ParameterInfo parameter = parametersInfo[i];
                      Type parameterType = parameter.ParameterType;
                      bool isByRef = false;
                      if (parameterType.IsByRef)
                      {
                          parameterType = parameterType.GetElementType();
                          isByRef = true;
                      }
  
                      Expression indexExpression = Expression.Constant(i);
  
                      Expression paramAccessorExpression = Expression.ArrayIndex(argsParameterExpression, indexExpression);
  
                      Expression argExpression = EnsureCastExpression(paramAccessorExpression, parameterType, !isByRef);
  
                      if (isByRef)
                      {
                          ParameterExpression variable = Expression.Variable(parameterType);
                          refParameterMap.Add(new ByRefParameter {Value = argExpression, Variable = variable, IsOut = parameter.IsOut});
  
                          argExpression = variable;
                      }
  
                      argsExpression[i] = argExpression;
                  }
              }
  
              Expression callExpression;
              if (method.IsConstructor)
              {
                  callExpression = Expression.New((ConstructorInfo)method, argsExpression);
              }
              else if (method.IsStatic)
              {
                  callExpression = Expression.Call((MethodInfo)method, argsExpression);
              }
              else
              {
                  Expression readParameter = EnsureCastExpression(targetParameterExpression, method.DeclaringType);
  
                  callExpression = Expression.Call(readParameter, (MethodInfo)method, argsExpression);
              }
  
              MethodInfo m = method as MethodInfo;
              if (m != null)
              {
                  if (m.ReturnType != typeof(void))
                  {
                      callExpression = EnsureCastExpression(callExpression, type);
                  }
                  else
                  {
                      callExpression = Expression.Block(callExpression, Expression.Constant(null));
                  }
              }
              else
              {
                  callExpression = EnsureCastExpression(callExpression, type);
              }
  
              if (refParameterMap.Count > 0)
              {
                  IList<ParameterExpression> variableExpressions = new List<ParameterExpression>();
                  IList<Expression> bodyExpressions = new List<Expression>();
                  foreach (ByRefParameter p in refParameterMap)
                  {
                      if (!p.IsOut)
                      {
                          bodyExpressions.Add(Expression.Assign(p.Variable, p.Value));
                      }
  
                      variableExpressions.Add(p.Variable);
                  }
  
                  bodyExpressions.Add(callExpression);
  
                  callExpression = Expression.Block(variableExpressions, bodyExpressions);
              }
  
              return callExpression;
          }
  
          public override Func<T> CreateDefaultConstructor<T>(Type type)
          {
              ValidationUtils.ArgumentNotNull(type, "type");
  
              // avoid error from expressions compiler because of abstract class
              if (type.IsAbstract())
              {
                  return () => (T)Activator.CreateInstance(type);
              }
  
              try
              {
                  Type resultType = typeof(T);
  
                  Expression expression = Expression.New(type);
  
                  expression = EnsureCastExpression(expression, resultType);
  
                  LambdaExpression lambdaExpression = Expression.Lambda(typeof(Func<T>), expression);
  
                  Func<T> compiled = (Func<T>)lambdaExpression.Compile();
                  return compiled;
              }
              catch
              {
                  // an error can be thrown if constructor is not valid on Win8
                  // will have INVOCATION_FLAGS_NON_W8P_FX_API invocation flag
                  return () => (T)Activator.CreateInstance(type);
              }
          }
  
          public override Func<T, object> CreateGet<T>(PropertyInfo propertyInfo)
          {
              ValidationUtils.ArgumentNotNull(propertyInfo, nameof(propertyInfo));
  
              Type instanceType = typeof(T);
              Type resultType = typeof(object);
  
              ParameterExpression parameterExpression = Expression.Parameter(instanceType, "instance");
              Expression resultExpression;
  
              MethodInfo getMethod = propertyInfo.GetGetMethod(true);
  
              if (getMethod.IsStatic)
              {
                  resultExpression = Expression.MakeMemberAccess(null, propertyInfo);
              }
              else
              {
                  Expression readParameter = EnsureCastExpression(parameterExpression, propertyInfo.DeclaringType);
  
                  resultExpression = Expression.MakeMemberAccess(readParameter, propertyInfo);
              }
  
              resultExpression = EnsureCastExpression(resultExpression, resultType);
  
              LambdaExpression lambdaExpression = Expression.Lambda(typeof(Func<T, object>), resultExpression, parameterExpression);
  
              Func<T, object> compiled = (Func<T, object>)lambdaExpression.Compile();
              return compiled;
          }
  
          public override Func<T, object> CreateGet<T>(FieldInfo fieldInfo)
          {
              ValidationUtils.ArgumentNotNull(fieldInfo, nameof(fieldInfo));
  
              ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source");
  
              Expression fieldExpression;
              if (fieldInfo.IsStatic)
              {
                  fieldExpression = Expression.Field(null, fieldInfo);
              }
              else
              {
                  Expression sourceExpression = EnsureCastExpression(sourceParameter, fieldInfo.DeclaringType);
  
                  fieldExpression = Expression.Field(sourceExpression, fieldInfo);
              }
  
              fieldExpression = EnsureCastExpression(fieldExpression, typeof(object));
  
              Func<T, object> compiled = Expression.Lambda<Func<T, object>>(fieldExpression, sourceParameter).Compile();
              return compiled;
          }
  
          public override Action<T, object> CreateSet<T>(FieldInfo fieldInfo)
          {
              ValidationUtils.ArgumentNotNull(fieldInfo, nameof(fieldInfo));
  
              // use reflection for structs
              // expression doesn't correctly set value
              if (fieldInfo.DeclaringType.IsValueType() || fieldInfo.IsInitOnly)
              {
                  return LateBoundReflectionDelegateFactory.Instance.CreateSet<T>(fieldInfo);
              }
  
              ParameterExpression sourceParameterExpression = Expression.Parameter(typeof(T), "source");
              ParameterExpression valueParameterExpression = Expression.Parameter(typeof(object), "value");
  
              Expression fieldExpression;
              if (fieldInfo.IsStatic)
              {
                  fieldExpression = Expression.Field(null, fieldInfo);
              }
              else
              {
                  Expression sourceExpression = EnsureCastExpression(sourceParameterExpression, fieldInfo.DeclaringType);
  
                  fieldExpression = Expression.Field(sourceExpression, fieldInfo);
              }
  
              Expression valueExpression = EnsureCastExpression(valueParameterExpression, fieldExpression.Type);
  
              BinaryExpression assignExpression = Expression.Assign(fieldExpression, valueExpression);
  
              LambdaExpression lambdaExpression = Expression.Lambda(typeof(Action<T, object>), assignExpression, sourceParameterExpression, valueParameterExpression);
  
              Action<T, object> compiled = (Action<T, object>)lambdaExpression.Compile();
              return compiled;
          }
  
          public override Action<T, object> CreateSet<T>(PropertyInfo propertyInfo)
          {
              ValidationUtils.ArgumentNotNull(propertyInfo, nameof(propertyInfo));
  
              // use reflection for structs
              // expression doesn't correctly set value
              if (propertyInfo.DeclaringType.IsValueType())
              {
                  return LateBoundReflectionDelegateFactory.Instance.CreateSet<T>(propertyInfo);
              }
  
              Type instanceType = typeof(T);
              Type valueType = typeof(object);
  
              ParameterExpression instanceParameter = Expression.Parameter(instanceType, "instance");
  
              ParameterExpression valueParameter = Expression.Parameter(valueType, "value");
              Expression readValueParameter = EnsureCastExpression(valueParameter, propertyInfo.PropertyType);
  
              MethodInfo setMethod = propertyInfo.GetSetMethod(true);
  
              Expression setExpression;
              if (setMethod.IsStatic)
              {
                  setExpression = Expression.Call(setMethod, readValueParameter);
              }
              else
              {
                  Expression readInstanceParameter = EnsureCastExpression(instanceParameter, propertyInfo.DeclaringType);
  
                  setExpression = Expression.Call(readInstanceParameter, setMethod, readValueParameter);
              }
  
              LambdaExpression lambdaExpression = Expression.Lambda(typeof(Action<T, object>), setExpression, instanceParameter, valueParameter);
  
              Action<T, object> compiled = (Action<T, object>)lambdaExpression.Compile();
              return compiled;
          }
          
          private Expression EnsureCastExpression(Expression expression, Type targetType, bool allowWidening = false)
          {
              Type expressionType = expression.Type;
              
              // check if a cast or conversion is required
              if (expressionType == targetType || (!expressionType.IsValueType() && targetType.IsAssignableFrom(expressionType)))
              {
                  return expression;
              }
  
              if (targetType.IsValueType())
              {
                  Expression convert = Expression.Unbox(expression, targetType);
  
                  if (allowWidening && targetType.IsPrimitive())
                  {
                      MethodInfo toTargetTypeMethod = typeof(Convert)
                          .GetMethod("To" + targetType.Name, new[] { typeof(object) });
  
  
gt:                     if (toTargetTypeMethod != null)