blob: 256d3e76d58954e5b42633e8455e0d29e73b7be7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Apache.Ignite.Core.Impl.Common
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using Apache.Ignite.Core.Binary;
using Apache.Ignite.Core.Common;
/// <summary>
/// Converts generic and non-generic delegates.
/// </summary>
[CLSCompliant(false)]
public static class DelegateConverter
{
/** */
private const string DefaultMethodName = "Invoke";
/** */
private static readonly MethodInfo ReadObjectMethod = typeof (IBinaryRawReader).GetMethod("ReadObject");
/** */
public static readonly MethodInfo ConvertArrayMethod = typeof(DelegateConverter).GetMethod(
"ConvertArray",
BindingFlags.Static | BindingFlags.NonPublic);
/** */
public static readonly MethodInfo ConvertToSbyteArrayMethod = typeof(DelegateConverter).GetMethod(
"ConvertToSbyteArray",
BindingFlags.Static | BindingFlags.Public);
/** */
public static readonly MethodInfo ConvertToUshortArrayMethod = typeof(DelegateConverter).GetMethod(
"ConvertToUshortArray",
BindingFlags.Static | BindingFlags.Public);
/** */
public static readonly MethodInfo ConvertToUintArrayMethod = typeof(DelegateConverter).GetMethod(
"ConvertToUintArray",
BindingFlags.Static | BindingFlags.Public);
/** */
public static readonly MethodInfo ConvertToUlongArrayMethod = typeof(DelegateConverter).GetMethod(
"ConvertToUlongArray",
BindingFlags.Static | BindingFlags.Public);
/// <summary>
/// Compiles a function without arguments.
/// </summary>
/// <param name="targetType">Type of the target.</param>
/// <returns>Compiled function that calls specified method on specified target.</returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static Func<object, object> CompileFunc(Type targetType)
{
var method = targetType.GetMethod(DefaultMethodName);
Debug.Assert(method != null, "method != null");
var targetParam = Expression.Parameter(typeof(object));
var targetParamConverted = Expression.Convert(targetParam, targetType);
var callExpr = Expression.Call(targetParamConverted, method);
var convertResultExpr = Expression.Convert(callExpr, typeof(object));
return Expression.Lambda<Func<object, object>>(convertResultExpr, targetParam).Compile();
}
/// <summary>
/// Compiles a function with arbitrary number of arguments.
/// </summary>
/// <typeparam name="T">Resulting delegate type.</typeparam>
/// <param name="targetType">Type of the target.</param>
/// <param name="argTypes">Argument types.</param>
/// <param name="convertToObject">
/// Flags that indicate whether func params and/or return value should be converted from/to object.
/// </param>
/// <param name="methodName">Name of the method.</param>
/// <returns>
/// Compiled function that calls specified method on specified target.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static T CompileFunc<T>(Type targetType, Type[] argTypes, bool[] convertToObject = null,
string methodName = null)
where T : class
{
var method = targetType.GetMethod(methodName ?? DefaultMethodName, argTypes);
return CompileFunc<T>(targetType, method, argTypes, convertToObject);
}
/// <summary>
/// Compiles a function with arbitrary number of arguments.
/// </summary>
/// <typeparam name="T">Resulting delegate type.</typeparam>
/// <param name="method">Method.</param>
/// <param name="targetType">Type of the target.</param>
/// <param name="argTypes">Argument types.</param>
/// <param name="convertToObject">
/// Flags that indicate whether func params and/or return value should be converted from/to object.
/// </param>
/// <returns>
/// Compiled function that calls specified method on specified target.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static T CompileFunc<T>(Type targetType, MethodInfo method, Type[] argTypes,
bool[] convertToObject = null)
where T : class
{
if (argTypes == null)
{
var args = method.GetParameters();
argTypes = new Type[args.Length];
for (int i = 0; i < args.Length; i++)
argTypes[i] = args[i].ParameterType;
}
Debug.Assert(convertToObject == null || (convertToObject.Length == argTypes.Length + 1));
Debug.Assert(method != null);
targetType = method.IsStatic ? null : (targetType ?? method.DeclaringType);
var targetParam = Expression.Parameter(typeof(object));
Expression targetParamConverted = null;
ParameterExpression[] argParams;
int argParamsOffset = 0;
if (targetType != null)
{
targetParamConverted = Expression.Convert(targetParam, targetType);
argParams = new ParameterExpression[argTypes.Length + 1];
argParams[0] = targetParam;
argParamsOffset = 1;
}
else
argParams = new ParameterExpression[argTypes.Length]; // static method
var argParamsConverted = new Expression[argTypes.Length];
for (var i = 0; i < argTypes.Length; i++)
{
if (convertToObject == null || convertToObject[i])
{
var argParam = Expression.Parameter(typeof (object));
argParams[i + argParamsOffset] = argParam;
argParamsConverted[i] = Expression.Convert(argParam, argTypes[i]);
}
else
{
var argParam = Expression.Parameter(argTypes[i]);
argParams[i + argParamsOffset] = argParam;
argParamsConverted[i] = argParam;
}
}
Expression callExpr = Expression.Call(targetParamConverted, method, argParamsConverted);
if (convertToObject == null || convertToObject[argTypes.Length])
callExpr = Expression.Convert(callExpr, typeof(object));
return Expression.Lambda<T>(callExpr, argParams).Compile();
}
/// <summary>
/// Compiles a function with a single object[] argument which maps array items to actual arguments.
/// </summary>
/// <param name="method">Method.</param>
/// <returns>
/// Compiled function that calls specified method.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static Func<object, object[], object> CompileFuncFromArray(MethodInfo method)
{
Debug.Assert(method != null);
Debug.Assert(method.DeclaringType != null);
var targetParam = Expression.Parameter(typeof(object));
var targetParamConverted = Expression.Convert(targetParam, method.DeclaringType);
var arrParam = Expression.Parameter(typeof(object[]));
var methodParams = method.GetParameters();
var argParams = new Expression[methodParams.Length];
for (var i = 0; i < methodParams.Length; i++)
{
var arrElem = Expression.ArrayIndex(arrParam, Expression.Constant(i));
argParams[i] = Convert(arrElem, methodParams[i].ParameterType);
}
Expression callExpr = Expression.Call(targetParamConverted, method, argParams);
if (callExpr.Type == typeof(void))
{
// Convert action to function
var action = Expression.Lambda<Action<object, object[]>>(callExpr, targetParam, arrParam).Compile();
return (obj, args) =>
{
action(obj, args);
return null;
};
}
callExpr = Expression.Convert(callExpr, typeof(object));
return Expression.Lambda<Func<object, object[], object>>(callExpr, targetParam, arrParam).Compile();
}
/// <summary>
/// Compiles a generic ctor with arbitrary number of arguments.
/// </summary>
/// <typeparam name="T">Result func type.</typeparam>
/// <param name="ctor">Constructor info.</param>
/// <param name="argTypes">Argument types.</param>
/// <param name="convertResultToObject">
/// Flag that indicates whether ctor return value should be converted to object.</param>
/// <param name="convertParamsFromObject">
/// Flag that indicates whether ctor args are object and should be converted to concrete type.</param>
/// <returns>
/// Compiled generic constructor.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static T CompileCtor<T>(ConstructorInfo ctor, Type[] argTypes, bool convertResultToObject = true,
bool convertParamsFromObject = true)
{
Debug.Assert(ctor != null);
var args = new ParameterExpression[argTypes.Length];
var argsConverted = new Expression[argTypes.Length];
for (var i = 0; i < argTypes.Length; i++)
{
if (convertParamsFromObject)
{
var arg = Expression.Parameter(typeof(object));
args[i] = arg;
argsConverted[i] = Expression.Convert(arg, argTypes[i]);
}
else
{
argsConverted[i] = args[i] = Expression.Parameter(argTypes[i]);
}
}
Expression ctorExpr = Expression.New(ctor, argsConverted); // ctor takes args of specific types
if (convertResultToObject)
ctorExpr = Expression.Convert(ctorExpr, typeof(object)); // convert ctor result to object
return Expression.Lambda<T>(ctorExpr, args).Compile(); // lambda takes args as objects
}
/// <summary>
/// Compiles a generic ctor with arbitrary number of arguments
/// that takes an uninitialized object as a first arguments.
/// </summary>
/// <typeparam name="T">Result func type.</typeparam>
/// <param name="ctor">Constructor info.</param>
/// <param name="argTypes">Argument types.</param>
/// <returns>
/// Compiled generic constructor.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static T CompileUninitializedObjectCtor<T>(ConstructorInfo ctor, Type[] argTypes)
{
Debug.Assert(ctor != null);
Debug.Assert(ctor.DeclaringType != null);
Debug.Assert(argTypes != null);
argTypes = new[] {typeof(object)}.Concat(argTypes).ToArray();
var helperMethod = new DynamicMethod(string.Empty, typeof(void), argTypes, ctor.Module, true);
var il = helperMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
if (ctor.DeclaringType.IsValueType)
il.Emit(OpCodes.Unbox, ctor.DeclaringType); // modify boxed copy
if (argTypes.Length > 1)
il.Emit(OpCodes.Ldarg_1);
if (argTypes.Length > 2)
il.Emit(OpCodes.Ldarg_2);
if (argTypes.Length > 3)
throw new NotSupportedException("Not supported: too many ctor args.");
il.Emit(OpCodes.Call, ctor);
il.Emit(OpCodes.Ret);
var constructorInvoker = helperMethod.CreateDelegate(typeof(T));
return (T) (object) constructorInvoker;
}
/// <summary>
/// Compiles a generic ctor with arbitrary number of arguments.
/// </summary>
/// <typeparam name="T">Result func type.</typeparam>
/// <param name="type">Type to be created by ctor.</param>
/// <param name="argTypes">Argument types.</param>
/// <param name="convertResultToObject">
/// Flag that indicates whether ctor return value should be converted to object.
/// </param>
/// <returns>
/// Compiled generic constructor.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static T CompileCtor<T>(Type type, Type[] argTypes, bool convertResultToObject = true)
{
var ctor = type.GetConstructor(argTypes);
return CompileCtor<T>(ctor, argTypes, convertResultToObject);
}
/// <summary>
/// Compiles a constructor that reads all arguments from a binary reader.
/// </summary>
/// <typeparam name="T">Result type</typeparam>
/// <param name="ctor">The ctor.</param>
/// <param name="innerCtorFunc">Function to retrieve reading constructor for an argument.
/// Can be null or return null, in this case the argument will be read directly via ReadObject.</param>
/// <returns></returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static Func<IBinaryRawReader, T> CompileCtor<T>(ConstructorInfo ctor,
Func<Type, ConstructorInfo> innerCtorFunc)
{
Debug.Assert(ctor != null);
var readerParam = Expression.Parameter(typeof (IBinaryRawReader));
var ctorExpr = GetConstructorExpression(ctor, innerCtorFunc, readerParam, typeof(T));
return Expression.Lambda<Func<IBinaryRawReader, T>>(ctorExpr, readerParam).Compile();
}
/// <summary>
/// Gets the constructor expression.
/// </summary>
/// <param name="ctor">The ctor.</param>
/// <param name="innerCtorFunc">The inner ctor function.</param>
/// <param name="readerParam">The reader parameter.</param>
/// <param name="resultType">Type of the result.</param>
/// <returns>
/// Ctor call expression.
/// </returns>
private static Expression GetConstructorExpression(ConstructorInfo ctor,
Func<Type, ConstructorInfo> innerCtorFunc, Expression readerParam, Type resultType)
{
var ctorParams = ctor.GetParameters();
var paramsExpr = new List<Expression>(ctorParams.Length);
foreach (var param in ctorParams)
{
var paramType = param.ParameterType;
var innerCtor = innerCtorFunc != null ? innerCtorFunc(paramType) : null;
if (innerCtor != null)
{
var readExpr = GetConstructorExpression(innerCtor, innerCtorFunc, readerParam, paramType);
paramsExpr.Add(readExpr);
}
else
{
var readMethod = ReadObjectMethod.MakeGenericMethod(paramType);
var readExpr = Expression.Call(readerParam, readMethod);
paramsExpr.Add(readExpr);
}
}
Expression ctorExpr = Expression.New(ctor, paramsExpr);
ctorExpr = Expression.Convert(ctorExpr, resultType);
return ctorExpr;
}
/// <summary>
/// Compiles the field setter.
/// </summary>
/// <param name="field">The field.</param>
/// <returns>Compiled field setter.</returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static Action<object, object> CompileFieldSetter(FieldInfo field)
{
Debug.Assert(field != null);
Debug.Assert(field.DeclaringType != null);
var targetParam = Expression.Parameter(typeof(object));
var valParam = Expression.Parameter(typeof(object));
var valParamConverted = Expression.Convert(valParam, field.FieldType);
var assignExpr = Expression.Call(GetWriteFieldMethod(field), targetParam, valParamConverted);
return Expression.Lambda<Action<object, object>>(assignExpr, targetParam, valParam).Compile();
}
/// <summary>
/// Compiles the property setter.
/// </summary>
/// <param name="prop">The property.</param>
/// <returns>Compiled property setter.</returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static Action<object, object> CompilePropertySetter(PropertyInfo prop)
{
Debug.Assert(prop != null);
Debug.Assert(prop.DeclaringType != null);
var targetParam = Expression.Parameter(typeof(object));
var targetParamConverted = Expression.Convert(targetParam, prop.DeclaringType);
var valParam = Expression.Parameter(typeof(object));
var valParamConverted = Expression.Convert(valParam, prop.PropertyType);
var fld = Expression.Property(targetParamConverted, prop);
var assignExpr = Expression.Assign(fld, valParamConverted);
return Expression.Lambda<Action<object, object>>(assignExpr, targetParam, valParam).Compile();
}
/// <summary>
/// Compiles the property setter.
/// </summary>
/// <param name="prop">The property.</param>
/// <returns>Compiled property setter.</returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static Func<object, object> CompilePropertyGetter(PropertyInfo prop)
{
Debug.Assert(prop != null);
Debug.Assert(prop.DeclaringType != null);
var targetParam = Expression.Parameter(typeof(object));
var targetParamConverted = prop.GetGetMethod().IsStatic
? null
// ReSharper disable once AssignNullToNotNullAttribute (incorrect warning)
: Expression.Convert(targetParam, prop.DeclaringType);
var fld = Expression.Property(targetParamConverted, prop);
var fldConverted = Expression.Convert(fld, typeof (object));
return Expression.Lambda<Func<object, object>>(fldConverted, targetParam).Compile();
}
/// <summary>
/// Compiles the property setter.
/// </summary>
/// <param name="field">The field.</param>
/// <returns>Compiled property setter.</returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static Func<object, object> CompileFieldGetter(FieldInfo field)
{
Debug.Assert(field != null);
Debug.Assert(field.DeclaringType != null);
var targetParam = Expression.Parameter(typeof(object));
var targetParamConverted = field.IsStatic
? null
: Expression.Convert(targetParam, field.DeclaringType);
var fld = Expression.Field(targetParamConverted, field);
var fldConverted = Expression.Convert(fld, typeof (object));
return Expression.Lambda<Func<object, object>>(fldConverted, targetParam).Compile();
}
/// <summary>
/// Gets a method to write a field (including private and readonly).
/// NOTE: Expression Trees can't write readonly fields.
/// </summary>
/// <param name="field">The field.</param>
/// <returns>Resulting MethodInfo.</returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static DynamicMethod GetWriteFieldMethod(FieldInfo field)
{
Debug.Assert(field != null);
var declaringType = field.DeclaringType;
Debug.Assert(declaringType != null);
var method = new DynamicMethod(string.Empty, null, new[] { typeof(object), field.FieldType },
declaringType, true);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
if (declaringType.IsValueType)
il.Emit(OpCodes.Unbox, declaringType); // modify boxed copy
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, field);
il.Emit(OpCodes.Ret);
return method;
}
/// <summary>
/// Gets the constructor with exactly matching signature.
/// <para />
/// Type.GetConstructor matches compatible ones (i.e. taking object instead of concrete type).
/// </summary>
/// <param name="type">The type.</param>
/// <param name="types">The argument types.</param>
/// <returns>Constructor info.</returns>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
public static ConstructorInfo GetConstructorExact(Type type, Type[] types)
{
Debug.Assert(type != null);
Debug.Assert(types != null);
foreach (var constructorInfo in type.GetConstructors(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
var ctorTypes = constructorInfo.GetParameters().Select(x => x.ParameterType);
if (ctorTypes.SequenceEqual(types))
return constructorInfo;
}
return null;
}
/// <summary>
/// Converts expression to a given type.
/// </summary>
private static Expression Convert(Expression value, Type targetType)
{
if (targetType.IsArray)
{
var elType = targetType.GetElementType();
Debug.Assert(elType != null);
if (elType == typeof(sbyte))
{
return Expression.Call(null, ConvertToSbyteArrayMethod, value);
}
if (elType == typeof(ushort))
{
return Expression.Call(null, ConvertToUshortArrayMethod, value);
}
if (elType == typeof(uint))
{
return Expression.Call(null, ConvertToUintArrayMethod, value);
}
if (elType == typeof(ulong))
{
return Expression.Call(null, ConvertToUlongArrayMethod, value);
}
if (elType != typeof(object))
{
var convertMethod = ConvertArrayMethod.MakeGenericMethod(targetType.GetElementType());
return Expression.Call(null, convertMethod, value);
}
return Expression.Convert(value, targetType);
}
// For byte/sbyte and the like, simple Convert fails
// E.g. the following does not work: (sbyte)(object)((byte)1)
// But this does: (sbyte)(byte)(object)((byte)1)
// So for every "unsupported" type like sbyte, ushort, uint, ulong
// we have to do an additional conversion
if (targetType == typeof(sbyte))
{
value = Expression.Convert(value, typeof(byte));
}
else if (targetType == typeof(ushort))
{
value = Expression.Convert(value, typeof(short));
}
else if (targetType == typeof(uint))
{
value = Expression.Convert(value, typeof(int));
}
else if (targetType == typeof(ulong))
{
value = Expression.Convert(value, typeof(long));
}
return Expression.Convert(value, targetType);
}
/// <summary>
/// Converts object array to typed array.
/// </summary>
// ReSharper disable once UnusedMember.Local (used by reflection).
private static T[] ConvertArray<T>(object arrObj)
{
var arr = arrObj as Array;
if (arr == null)
{
return null;
}
var res = new T[arr.Length];
Array.Copy(arr, res, arr.Length);
return res;
}
/// <summary>
/// Converts to sbyte array.
/// </summary>
// ReSharper disable once UnusedMember.Global
public static sbyte[] ConvertToSbyteArray(object arrObj)
{
return ConvertValueTypeArray<byte, sbyte>(arrObj, 1);
}
/// <summary>
/// Converts to ushort array.
/// </summary>
// ReSharper disable once UnusedMember.Global
public static ushort[] ConvertToUshortArray(object arrObj)
{
return ConvertValueTypeArray<short, ushort>(arrObj, 2);
}
/// <summary>
/// Converts to uint array.
/// </summary>
// ReSharper disable once UnusedMember.Global
public static uint[] ConvertToUintArray(object arrObj)
{
return ConvertValueTypeArray<int, uint>(arrObj, 4);
}
/// <summary>
/// Converts to ulong array.
/// </summary>
// ReSharper disable once UnusedMember.Global
public static ulong[] ConvertToUlongArray(object arrObj)
{
return ConvertValueTypeArray<long, ulong>(arrObj, 8);
}
/// <summary>
/// Converts value type array to another type using direct copy.
/// </summary>
private static T[] ConvertValueTypeArray<TFrom, T>(object arrObj, int elementSize)
{
if (arrObj == null)
{
return null;
}
var arr = arrObj as TFrom[];
if (arr == null)
{
throw new IgniteException(string.Format("Can't convert '{0}' to '{1}'", arrObj.GetType(), typeof(T[])));
}
var res = new T[arr.Length];
Buffer.BlockCopy(arr, 0, res, 0, arr.Length * elementSize);
return res;
}
}
}