using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace ScriptNET.Runtime { /// /// Base implementation of object binder /// public partial class ObjectBinder : IObjectBinder { #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 [Bindable(false)] public ObjectBinder() { 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(mutant); Getters.Add(new NestedTypeGetter(this)); //Getters.Add(new NameSpaceGetter()); Setters.Add(property); Setters.Add(field); Setters.Add(@event); Setters.Add(sriptable); //Setters.Add(mutant); } #endregion #region IObjectBinder Members /// /// Binds to constructor /// /// Type /// Arguments for constructor /// ConstructorBind or null public IObjectBind 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 ConstructorBind(defaultConstructor, null); } IEnumerable constructors = targetType .GetConstructors(ConstructorFilter) .Where(c => CanBind(c)); for (int i = 0; i < parameterStrategies.Count; i++) { ComposeParameterStrategy strategy = parameterStrategies[i]; foreach (ConstructorInfo constructor in constructors) { ParameterInfo[] parameterInfos = constructor.GetParameters(); object[] convertedArguments = ComposeParameters(arguments, parameterInfos, strategy.Predicate, strategy.Converter); if (convertedArguments != null) { return new ConstructorBind(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 IObjectBind BindToMethod(object target, string methodName, Type[] genericParameters, object[] arguments) { 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 IObjectBind 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 MethodBind(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 IObjectBind 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)); } } for (int i = 0; i < parameterStrategies.Count; i++) { ComposeParameterStrategy strategy = parameterStrategies[i]; foreach (MethodInfo method in methods) { MethodInfo actualMethod = method; if (method.IsGenericMethod) { actualMethod = method.MakeGenericMethod(genericParameters); } ParameterInfo[] parameterInfos = actualMethod.GetParameters(); object[] convertedArguments = ComposeParameters(arguments, parameterInfos, strategy.Predicate, strategy.Converter); if (convertedArguments != null) { return new MethodBind(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 IObjectBind 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 IMemberBind BindToMember(object target, string memberName, bool throwNotFound) { return new LateBoundMember(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"); Type type = instance as Type; if (type == null) type = instance.GetType(); foreach (IGetter getter in Getters) { object rez = getter.Get(name, instance, type, arguments); if (rez != NoResult) return rez; } if (throwNotFound) throw new ScriptIdNotFoundException("Member " + name + " not found"); else 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"); Type type = instance as Type; if (type == null) type = instance.GetType(); foreach (ISetter setter in Setters) { object rez = setter.Set(name, instance, type, value, arguments); if (rez != NoResult) return rez; } if (throwNotFound) throw new ScriptIdNotFoundException("Member " + name + " not found"); else 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) { 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 MethodInfo mi = valueType.GetMethod("op_Implicit", BindingFlags.Static | BindingFlags.Public, null, new Type[] { valueType }, null); if (mi == null) { mi = valueType.GetMethod("op_Explicit", BindingFlags.Static | BindingFlags.Public, null, new Type[] { valueType }, null); } if (mi != null && mi.ReturnType == targetType) { return mi.Invoke(value, new object[] { value }); } //Convertible if (value is IConvertible) { return Convert.ChangeType(value, targetType, System.Globalization.CultureInfo.CurrentCulture); } 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) { BindableAttribute bindable = member.GetCustomAttributes(typeof(BindableAttribute), true).OfType().FirstOrDefault(); if (bindable == null) bindable = member.DeclaringType.GetCustomAttributes(typeof(BindableAttribute), true).OfType().FirstOrDefault(); if (bindable == null) return true; return bindable.CanBind; } #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; object[] result = new object[arguments.Length]; for (int i = 0; i < parameters.Length; i++) { Type parameterType = parameters[i].ParameterType; object argument = arguments[i]; if (predicate(argument, parameterType)) { object converted = converter(argument, parameterType); if (converted == ObjectBinder.NoResult) return null; result[i] = converted; } 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 } }