using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Jint.Native;
using Jint.Native.Function;
namespace Jint.Runtime.Interop
{
///
/// Represents a FunctionInstance wrapper around a CLR method. This is used by user to pass
/// custom methods to the engine.
///
public sealed class DelegateWrapper : FunctionInstance
{
private readonly Delegate _d;
public DelegateWrapper(Engine engine, Delegate d) : base(engine, null, null, false)
{
_d = d;
Prototype = engine.Function.PrototypeObject;
}
public override JsValue Call(JsValue thisObject, JsValue[] jsArguments)
{
var parameterInfos = _d.GetMethodInfo().GetParameters();
bool delegateContainsParamsArgument = parameterInfos.Any(p => p.HasAttribute());
int delegateArgumentsCount = parameterInfos.Length;
int delegateNonParamsArgumentsCount = delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount;
int jsArgumentsCount = jsArguments.Length;
int jsArgumentsWithoutParamsCount = Math.Min(jsArgumentsCount, delegateNonParamsArgumentsCount);
var parameters = new object[delegateArgumentsCount];
// convert non params parameter to expected types
for (var i = 0; i < jsArgumentsWithoutParamsCount; i++)
{
var parameterType = parameterInfos[i].ParameterType;
if (parameterType == typeof (JsValue))
{
parameters[i] = jsArguments[i];
}
else
{
parameters[i] = Engine.ClrTypeConverter.Convert(
jsArguments[i].ToObject(),
parameterType,
CultureInfo.InvariantCulture);
}
}
// assign null to parameters not provided
for (var i = jsArgumentsWithoutParamsCount; i < delegateNonParamsArgumentsCount; i++)
{
if (parameterInfos[i].ParameterType.IsValueType())
{
parameters[i] = Activator.CreateInstance(parameterInfos[i].ParameterType);
}
else
{
parameters[i] = null;
}
}
// assign params to array and converts each objet to expected type
if(delegateContainsParamsArgument)
{
int paramsArgumentIndex = delegateArgumentsCount - 1;
int paramsCount = Math.Max(0, jsArgumentsCount - delegateNonParamsArgumentsCount);
object[] paramsParameter = new object[paramsCount];
var paramsParameterType = parameterInfos[paramsArgumentIndex].ParameterType.GetElementType();
for (var i = paramsArgumentIndex; i < jsArgumentsCount; i++)
{
var paramsIndex = i - paramsArgumentIndex;
if (paramsParameterType == typeof(JsValue))
{
paramsParameter[paramsIndex] = jsArguments[i];
}
else
{
paramsParameter[paramsIndex] = Engine.ClrTypeConverter.Convert(
jsArguments[i].ToObject(),
paramsParameterType,
CultureInfo.InvariantCulture);
}
}
parameters[paramsArgumentIndex] = paramsParameter;
}
try
{
return JsValue.FromObject(Engine, _d.DynamicInvoke(parameters));
}
catch (TargetInvocationException exception)
{
var meaningfulException = exception.InnerException ?? exception;
var handler = Engine.Options._ClrExceptionsHandler;
if (handler != null && handler(meaningfulException))
{
throw new JavaScriptException(Engine.Error, meaningfulException.Message);
}
throw meaningfulException;
}
}
}
}