using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Jint.Native; using Jint.Native.Array; using Jint.Native.Function; namespace Jint.Runtime.Interop { public sealed class MethodInfoFunctionInstance : FunctionInstance { private readonly MethodInfo[] _methods; public MethodInfoFunctionInstance(Engine engine, MethodInfo[] methods) : base(engine, null, null, false) { _methods = methods; Prototype = engine.Function.PrototypeObject; } public override JsValue Call(JsValue thisObject, JsValue[] arguments) { return Invoke(_methods, thisObject, arguments); } public JsValue Invoke(MethodInfo[] methodInfos, JsValue thisObject, JsValue[] jsArguments) { var arguments = ProcessParamsArrays(jsArguments, methodInfos); var methods = TypeConverter.FindBestMatch(Engine, methodInfos, arguments).ToList(); var converter = Engine.ClrTypeConverter; foreach (var method in methods) { var parameters = new object[arguments.Length]; var argumentsMatch = true; for (var i = 0; i < arguments.Length; i++) { var parameterType = method.GetParameters()[i].ParameterType; if (parameterType == typeof(JsValue)) { parameters[i] = arguments[i]; } else if (parameterType == typeof(JsValue[]) && arguments[i].IsArray()) { // Handle specific case of F(params JsValue[]) var arrayInstance = arguments[i].AsArray(); var len = TypeConverter.ToInt32(arrayInstance.Get("length")); var result = new JsValue[len]; for (var k = 0; k < len; k++) { var pk = k.ToString(); result[k] = arrayInstance.HasProperty(pk) ? arrayInstance.Get(pk) : JsValue.Undefined; } parameters[i] = result; } else { if (!converter.TryConvert(arguments[i].ToObject(), parameterType, CultureInfo.InvariantCulture, out parameters[i])) { argumentsMatch = false; break; } var lambdaExpression = parameters[i] as LambdaExpression; if (lambdaExpression != null) { parameters[i] = lambdaExpression.Compile(); } } } if (!argumentsMatch) { continue; } // todo: cache method info return JsValue.FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters.ToArray())); } throw new JavaScriptException(Engine.TypeError, "No public methods with the specified arguments were found."); } /// /// Reduces a flat list of parameters to a params array /// private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, IEnumerable methodInfos) { foreach (var methodInfo in methodInfos) { var parameters = methodInfo.GetParameters(); if (!parameters.Any(p => p.HasAttribute())) continue; var nonParamsArgumentsCount = parameters.Length - 1; if (jsArguments.Length < nonParamsArgumentsCount) continue; var newArgumentsCollection = jsArguments.Take(nonParamsArgumentsCount).ToList(); var argsToTransform = jsArguments.Skip(nonParamsArgumentsCount).ToList(); if (argsToTransform.Count == 1 && argsToTransform.FirstOrDefault().IsArray()) continue; var jsArray = Engine.Array.Construct(Arguments.Empty); Engine.Array.PrototypeObject.Push(jsArray, argsToTransform.ToArray()); newArgumentsCollection.Add(new JsValue(jsArray)); return newArgumentsCollection.ToArray(); } return jsArguments; } } }