/* * Copyright © 2011, Petro Protsyk, Denys Vuika * * Licensed 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. */ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Scripting.SSharp.Runtime.Reflection; namespace Scripting.SSharp.Runtime.Promotion { /// /// Base implementation of object binder /// public partial class ObjectBinding : IObjectBinding { #region Fields /// /// Strategies for converting parameters during method binding /// protected readonly List ParameterStrategies = new List(); /// /// Chain of getters /// protected readonly List Getters = new List(); /// /// Chain of setters /// protected readonly List Setters = new List(); /// /// Empty types constant (empty array of types). /// protected static readonly Type[] EmptyTypes = new Type[0]; /// /// BindingFlags for constructor binding /// protected internal static BindingFlags ConstructorFilter = BindingFlags.Public | BindingFlags.Instance; /// /// BindingFlags for method binding /// protected internal static BindingFlags MethodFilter = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; /// /// BindingFlags for property binding /// protected internal static BindingFlags PropertyFilter = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; /// /// BindingFlags for field binding /// protected internal static BindingFlags FieldFilter = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; /// /// BindingFlags for nested type binding /// protected internal static BindingFlags NestedTypeFilter = BindingFlags.Public; /// /// Signals that no result is calculated /// protected static readonly object NoResult = new object(); #endregion #region Constructor [Promote(false)] public ObjectBinding() { ParameterStrategies.Add(new ComposeParameterStrategy(ComposeParametersExactPredicate, ComposeParametersExactConverter)); ParameterStrategies.Add(new ComposeParameterStrategy(ComposeParametersStrictPredicate, ComposeParametersStrictConverter)); ParameterStrategies.Add(new ComposeParameterStrategy(ComposeParametersWeekPredicate, ComposeParametersWeekConverter)); IHandler property = new PropertyHandler(this); IHandler field = new FieldHandler(this); IHandler @event = new EventHandler(this); //IHandler mutant = new MutantHandler(); IHandler sriptable = new ScriptableHandler(this); Getters.Add(property); Getters.Add(field); Getters.Add(@event); Getters.Add(new MethodGetter(this)); Getters.Add(sriptable); Getters.Add(new NestedTypeGetter(this)); //Getters.Add(new NameSpaceGetter()); Setters.Add(property); Setters.Add(field); Setters.Add(@event); Setters.Add(sriptable); } #endregion #region IObjectBinder Members /// /// Binds to constructor /// /// Type /// Arguments for constructor /// ConstructorBind or null public IBinding BindToConstructor(Type targetType, object[] arguments) { //Use Default Constructor if (arguments == null || arguments.Length == 0) { ConstructorInfo defaultConstructor = targetType.GetConstructor(ConstructorFilter, null, EmptyTypes, null); if (defaultConstructor == null || !CanBind(defaultConstructor)) return null; //return new ConstructorBind(target, null, null); return new ConstructorBinding(defaultConstructor, null); } var constructors = targetType.GetConstructors(ConstructorFilter).Where(CanBind); for (int i = 0; i < ParameterStrategies.Count; i++) { var strategy = ParameterStrategies[i]; foreach (var constructor in constructors) { var parameterInfos = constructor.GetParameters(); var convertedArguments = ComposeParameters(arguments, parameterInfos, strategy.Predicate, strategy.Converter); if (convertedArguments != null) { return new ConstructorBinding(constructor, convertedArguments); } } } return null; //return new ConstructorBind(target, null, null); } /// /// Binds to method /// /// instance on an object /// name of the method /// for generic methods should specify types other vise empty array of type /// arguments for method /// MethodBind or null if binding is not possible public IBinding BindToMethod(object target, string methodName, Type[] genericParameters, object[] arguments) { // TODO: Denis: just a quick dirty optimization of "empty" methods //if (genericParameters == null && arguments.Length == 0) //{ // Type targetType = target as Type; // if (targetType == null) targetType = target.GetType(); // IBinding cachedBinding = MethodProvider.GetBinding(targetType, methodName); // if (cachedBinding != null) // return cachedBinding; // else // { // IBinding binding = BindToMethod(target, mi => mi.Name == methodName && mi.GetParameters().Length == arguments.Length && CanBind(mi), genericParameters, arguments); // MethodProvider.AddBinding(targetType, methodName, binding); // return binding; // } //} return BindToMethod(target, mi => mi.Name == methodName && mi.GetParameters().Length == arguments.Length && CanBind(mi), genericParameters, arguments); } /// /// Binds to method /// /// instance on an object /// specific method /// arguments for method /// MethodBind or null if binding is not possible public IBinding BindToMethod(object target, MethodInfo method, object[] arguments) { for (int i = 0; i < ParameterStrategies.Count; i++) { ComposeParameterStrategy strategy = ParameterStrategies[i]; ParameterInfo[] parameterInfos = method.GetParameters(); object[] convertedArguments = ComposeParameters(arguments, parameterInfos, strategy.Predicate, strategy.Converter); if (convertedArguments != null) { return new MethodBinding(method, target, convertedArguments); } } return null; } /// /// Binds to method of a given object including interface methods /// /// instance on an object /// Predicate to select methods /// for generic methods should specify types other vise empty array of type /// arguments for method /// MethodBind or null if binding is not possible protected IBinding BindToMethod(object target, Func methodSelector, Type[] genericParameters, object[] arguments) { Type targetType = target as Type; bool processInterface = false; if (targetType == null) { targetType = target.GetType(); processInterface = true; } IEnumerable methods = targetType.GetMethods(MethodFilter).Where(methodSelector); //Process interfaces only if target is not a Type if (processInterface) { foreach (Type interfaceType in targetType.GetInterfaces()) { methods = methods.Concat(interfaceType.GetMethods(MethodFilter).Where(methodSelector)); } } return BindToMethods(target, genericParameters, arguments, methods); } protected IBinding BindToMethods(object target, Type[] genericParameters, object[] arguments, IEnumerable methods) { for (int i = 0; i < ParameterStrategies.Count; i++) { ComposeParameterStrategy strategy = ParameterStrategies[i]; foreach (MethodInfo method in methods) { MethodInfo actualMethod = method; if (method.IsGenericMethod) { if (genericParameters == null || method.GetGenericArguments().Length != genericParameters.Length) continue; actualMethod = method.MakeGenericMethod(genericParameters); } ParameterInfo[] parameterInfos = actualMethod.GetParameters(); object[] convertedArguments = ComposeParameters(arguments, parameterInfos, strategy.Predicate, strategy.Converter); if (convertedArguments != null) { return new MethodBinding(actualMethod, target, convertedArguments); } } } return null; } /// /// Binds to index property /// /// instance on an object /// arguments /// if true binds to setter, otherwise to getter /// MethodBind or null if binding is not possible public IBinding BindToIndex(object target, object[] arguments, bool setter) { if (setter) { return BindToMethod(target, mi => (mi.Name == "set_Item" || mi.Name == "Set" ) && mi.GetParameters().Length == arguments.Length, null, arguments); } else { return BindToMethod(target, mi => (mi.Name == "get_Item" || mi.Name == "Get" ) && mi.GetParameters().Length == arguments.Length, null, arguments); } } /// /// Late binding to any member of a given object /// /// instance of a object /// name of the method to bind /// if true will throw member not found exception at first attempt to invoke the binding /// LateBoundMember public IMemberBinding BindToMember(object target, string memberName, bool throwNotFound) { return new DelayedMemberBinding(this, target, memberName, throwNotFound); } /// /// Gets value of the member /// /// member name /// instance /// /// optional arguments /// value protected internal object Get(string name, object instance, bool throwNotFound, params object[] arguments) { if (instance == null) throw new ArgumentNullException("instance"); if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); var type = instance as Type ?? instance.GetType(); foreach (var getter in Getters) { var rez = getter.Get(name, instance, type, arguments); if (rez != NoResult) return rez; } if (throwNotFound) throw new ScriptIdNotFoundException(string.Format(Strings.MemberNotFound, name)); return RuntimeHost.NullValue; } /// /// Sets the value to the member /// /// member name /// instance /// value /// boolean /// optional arguments /// actual value. note that value may be transformed during setting protected internal object Set(string name, object instance, object value, bool throwNotFound, params object[] arguments) { if (instance == null) throw new ArgumentNullException("instance"); if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); var type = instance as Type ?? instance.GetType(); foreach (var setter in Setters) { var rez = setter.Set(name, instance, type, value, arguments); if (rez != NoResult) return rez; } if (throwNotFound) throw new ScriptIdNotFoundException(string.Format(Strings.MemberNotFound, name)); return RuntimeHost.NullValue; } /// /// Method used to convert value to target type. Should be used for any conversion during script execution /// /// value to convert or null /// target type /// Converted value or NoResult constant if conversion impossible public object ConvertTo(object value, Type targetType) { return ConvertToStatic(value, targetType); } /// /// Method used to convert value to target type. Should be used for any conversion during script execution. /// /// This method should be used by derived class for performance instead of ConvertTo /// /// value to convert or null /// target type /// Converted value or NoResult constant if conversion impossible protected static object ConvertToStatic(object value, Type targetType) { //TODO: Cache conversion method if (value == null) return value; if (targetType == typeof(object)) return value; Type valueType = value.GetType(); //Interface if (targetType.IsInterface) { if (valueType .GetInterfaces() .Where(i => i.FullName == targetType.FullName) .Count() != 0) return value; //return new ExplicitInterface(value, targetType); } //Type Match if (targetType.IsAssignableFrom(valueType)) { return value; } //Conversion operators var mi = MethodProvider.GetConversionMethod(valueType,targetType); if (mi != null) return mi.Invoke(value, new[] { value }); //NOTE: ref, out if (targetType.IsByRef) { return targetType.GetElementType() == valueType ? value : ConvertToStatic(value, targetType.GetElementType()); } //Convertible if (value is IConvertible) { try { return Convert.ChangeType(value, targetType, System.Globalization.CultureInfo.CurrentCulture); } catch (InvalidCastException) { return NoResult; } } //TODO: Improve Scripting.SSharp.Parser.Ast.ScriptFunctionDefinition f = value as Scripting.SSharp.Parser.Ast.ScriptFunctionDefinition; if (f != null) { try { return f.AsDelegate(targetType); } catch (InvalidCastException) { return NoResult; } } return NoResult; } /// /// Checks binding conditions for given type member. Could be overriden in derived classes. /// /// This implementation uses BindableAttribute for evaluating conditions /// /// Instance of MemberInfo /// true if member could participate in binding public virtual bool CanBind(MemberInfo member) { return PromotionProvider.IsPromoted(member); } #endregion #region ComposeParameterStrategy protected delegate bool CanConvertTypePredicate(object value, Type targetType); protected delegate object ConvertTypeMethod(object value, Type targetType); protected static object[] ComposeParameters(object[] arguments, ParameterInfo[] parameters, CanConvertTypePredicate predicate, ConvertTypeMethod converter) { if (arguments.Length != parameters.Length) return null; var result = new object[arguments.Length]; for (int i = 0; i < parameters.Length; i++) { var parameterType = parameters[i].ParameterType; var argument = arguments[i]; //NOTE: ref, out //out and ref parameters handling ScopeValueReference vr = null; if (parameters[i].ParameterType.IsByRef) { vr = (ScopeValueReference)argument; argument = vr.Value; } if (predicate(argument, parameterType)) { var converted = converter(argument, parameterType); if (converted == NoResult) return null; //NOTE: ref, out if (vr == null) result[i] = converted; else { vr.ConvertedValue = converted; result[i] = vr; } } else { return null; } } return result; } protected class ComposeParameterStrategy { public CanConvertTypePredicate Predicate { get; private set; } public ConvertTypeMethod Converter { get; private set; } public ComposeParameterStrategy(CanConvertTypePredicate predicate, ConvertTypeMethod converter) { Predicate = predicate; Converter = converter; } } #endregion #region Strategies protected static bool ComposeParametersExactPredicate(object value, Type targetType) { return value == null || value.GetType() == targetType; } protected static object ComposeParametersExactConverter(object value, Type targetType) { return value; } protected static bool ComposeParametersStrictPredicate(object value, Type targetType) { if (targetType == typeof(object)) return false; if (value == null) return true; Type vType = value.GetType(); if (vType == targetType) return true; if (vType == typeof(Byte)) { if (targetType == typeof(Int16)) return true; if (targetType == typeof(Int32)) return true; if (targetType == typeof(Int64)) return true; if (targetType == typeof(Double)) return true; if (targetType == typeof(Single)) return true; } if (vType == typeof(Int16)) { if (targetType == typeof(Byte)) return true; if (targetType == typeof(Int32)) return true; if (targetType == typeof(Int64)) return true; if (targetType == typeof(Double)) return true; if (targetType == typeof(Single)) return true; } if (vType == typeof(Int32)) { if (targetType == typeof(Byte)) return true; if (targetType == typeof(Int16)) return true; if (targetType == typeof(Int64)) return true; if (targetType == typeof(Double)) return true; if (targetType == typeof(Single)) return true; } if (vType == typeof(Int64)) { if (targetType == typeof(Byte)) return true; if (targetType == typeof(Int16)) return true; if (targetType == typeof(Int32)) return true; if (targetType == typeof(Double)) return true; if (targetType == typeof(Single)) return true; } if (vType == typeof(Single)) { if (targetType == typeof(Byte)) return true; if (targetType == typeof(Int16)) return true; if (targetType == typeof(Int32)) return true; if (targetType == typeof(Int64)) return true; if (targetType == typeof(Double)) return true; } if (vType == typeof(Double)) { if (targetType == typeof(Byte)) return true; if (targetType == typeof(Int16)) return true; if (targetType == typeof(Int32)) return true; if (targetType == typeof(Int64)) return true; if (targetType == typeof(Single)) return true; } return vType.IsSubclassOf(targetType); } protected static object ComposeParametersStrictConverter(object value, Type targetType) { return ConvertToStatic(value, targetType); } protected static bool ComposeParametersWeekPredicate(object value, Type targetType) { return true; } protected static object ComposeParametersWeekConverter(object value, Type targetType) { return ConvertToStatic(value, targetType); } #endregion #region Private Interfaces /// /// Getter /// protected interface IGetter { object Get(string name, object instance, Type type, params object[] arguments); } /// /// Setter /// protected interface ISetter { object Set(string name, object instance, Type type, object value, params object[] arguments); } /// /// Getter and Setter /// protected interface IHandler : IGetter, ISetter { } #endregion } }