using System; using System.Collections.Generic; using System.Linq; using System.Text; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using HeuristicLab.Common; using System.Reflection; using System.Reflection.Emit; using System.ComponentModel; using System.Linq.Expressions; namespace HeuristicLab.Core { [StorableClass] public sealed class ItemBinding : IItemBinding { private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; [Storable] private IDeepCloneable target; [Storable] private string targetPath; [Storable] private IDeepCloneable source; [Storable] private string sourcePath; [Storable] private bool bound; [Storable] private LambdaExpression bindingExpression; private Delegate bindingFunc; private static object evhCacheLock = new object(); private static Dictionary eventHandlerCache = new Dictionary(); [StorableConstructor] private ItemBinding(bool deserializing) { } private ItemBinding(ItemBinding original, Cloner cloner) { this.target = cloner.Clone(original.target); this.targetPath = original.targetPath; this.source = cloner.Clone(original.source); this.sourcePath = original.sourcePath; this.bindingExpression = original.bindingExpression; if (this.bindingExpression != null) this.bindingFunc = this.bindingExpression.Compile(); if (original.bound) Bind(); } public ItemBinding(IDeepCloneable target, string targetPath, IDeepCloneable source, string sourcePath) { this.target = target; this.targetPath = targetPath; this.source = source; this.sourcePath = sourcePath; this.bound = false; } public ItemBinding(IDeepCloneable target, string targetPath, IDeepCloneable source, string sourcePath, LambdaExpression func) : this(target, targetPath, source, sourcePath) { this.bindingExpression = func; this.bindingFunc = this.bindingExpression.Compile(); } public IDeepCloneable Clone(Cloner cloner) { return new ItemBinding(this, cloner); } public object Clone() { return Clone(new Cloner()); } [StorableHook(HookType.AfterDeserialization)] private void AfterDeserialization() { if (bound) RegisterEventHandlers(); if (bindingExpression != null) bindingFunc = bindingExpression.Compile(); } public void Bind() { RegisterEventHandlers(); bound = true; ExecuteBinding(null); } public void Unbind() { DeregisterEventHandlers(); bound = false; } private void RegisterEventHandlers() { string[] properties = sourcePath.Split('.'); object current = source; foreach (string property in properties) { TryHookToChangedEvent(current, property); MethodInfo mInfo = current.GetType().GetProperty(property, Flags).GetGetMethod(true); object next = mInfo.Invoke(current, null); if (next == null) return; current = next; } } private void DeregisterEventHandlers() { string[] properties = sourcePath.Split('.'); object current = source; foreach (string property in properties) { TryUnhookFromChangedEvent(current, property); MethodInfo mInfo = current.GetType().GetProperty(property, Flags).GetGetMethod(true); object next = mInfo.Invoke(current, null); if (next == null) return; current = next; } } // DO NOT RENAME (string reference to this method name in CreateAndAddDynamicEventHandler) private void ExecuteBinding(object sender) { string[] sourceProperties = sourcePath.Split('.'); object cSource = source; #region Navigate to source property (readd bindings if necessary) bool reregister = false; for (int i = 0; i < sourceProperties.Length; i++) { if (cSource == null) return; string property = sourceProperties[i]; if (reregister) TryHookToChangedEvent(cSource, property); if (cSource == sender) reregister = true; PropertyInfo pInfo = cSource.GetType().GetProperty(property, Flags); if (pInfo == null) return; object next = pInfo.GetGetMethod(true).Invoke(cSource, null); cSource = next; } #endregion string[] targetProperties = targetPath.Split('.'); object cTarget = target; MethodInfo targetSetter = null; #region Navigate to target property for (int i = 0; i < targetProperties.Length - 1; i++) { PropertyInfo pInfo = cTarget.GetType().GetProperty(targetProperties[i], Flags); if (pInfo == null) return; cTarget = pInfo.GetGetMethod(true).Invoke(cTarget, null); if (cTarget == null) return; } targetSetter = cTarget.GetType().GetProperty(targetProperties.Last(), Flags).GetSetMethod(true); if (targetSetter == null) return; #endregion if (bindingFunc != null) { targetSetter.Invoke(cTarget, new object[] { bindingFunc.DynamicInvoke(new object[] { cSource }) }); } else if (cSource == null && !targetSetter.GetParameters().First().ParameterType.IsValueType || cSource != null && cSource.GetType().IsValueType) targetSetter.Invoke(cTarget, new object[] { cSource }); else if (cSource is ICloneable) targetSetter.Invoke(cTarget, new object[] { ((ICloneable)cSource).Clone() }); else throw new InvalidOperationException("Can only bind to targets which are either a ValueType or an ICloneable."); } private bool TryHookToChangedEvent(object obj, string property) { INotifyPropertyChanged npc = (obj as INotifyPropertyChanged); if (npc != null) { npc.PropertyChanged += new PropertyChangedEventHandler(source_PropertyChanged); return true; } else { EventInfo eInfo = obj.GetType().GetEvent(property + "Changed", Flags); if (eInfo != null) { Type[] methodParams = eInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray(); if (!eventHandlerCache.ContainsKey(methodParams[1])) CreateAndAddDynamicEventHandler(eInfo, methodParams); eInfo.AddEventHandler(obj, eventHandlerCache[methodParams[1]].CreateDelegate(eInfo.EventHandlerType, this)); } return eInfo != null; } } private bool TryUnhookFromChangedEvent(object obj, string property) { INotifyPropertyChanged npc = (obj as INotifyPropertyChanged); if (npc != null) { npc.PropertyChanged -= new PropertyChangedEventHandler(source_PropertyChanged); return true; } else { EventInfo eInfo = obj.GetType().GetEvent(property, Flags); if (eInfo != null) { Type[] methodParams = eInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray(); if (eventHandlerCache.ContainsKey(methodParams[1])) eInfo.RemoveEventHandler(obj, eventHandlerCache[methodParams[1]].CreateDelegate(eInfo.EventHandlerType, this)); } return eInfo != null; } } private void source_PropertyChanged(object sender, PropertyChangedEventArgs e) { ExecuteBinding(sender); } private void CreateAndAddDynamicEventHandler(EventInfo eInfo, Type[] methodParams) { lock (evhCacheLock) { if (!eventHandlerCache.ContainsKey(methodParams[1])) { Type[] instanceMethodParams = new Type[] { GetType() }.Concat(methodParams).ToArray(); DynamicMethod dynamicEventHandler = new DynamicMethod("source_Changed", null, instanceMethodParams, GetType()); MethodInfo executeBindingMethodInfo = GetType().GetMethod("ExecuteBinding", Flags); ILGenerator iLG = dynamicEventHandler.GetILGenerator(); iLG.Emit(OpCodes.Ldarg_0); // load 'this' pointer iLG.Emit(OpCodes.Ldarg_1); // load sender parameter iLG.Emit(OpCodes.Call, executeBindingMethodInfo); // call method to execute the binding iLG.Emit(OpCodes.Ret); eventHandlerCache.Add(methodParams[1], dynamicEventHandler); } } } } }