[4787] | 1 | using System;
|
---|
| 2 | using System.Collections.Generic;
|
---|
| 3 | using System.Linq;
|
---|
| 4 | using System.Text;
|
---|
| 5 | using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
|
---|
| 6 | using HeuristicLab.Common;
|
---|
| 7 | using System.Reflection;
|
---|
| 8 | using System.Reflection.Emit;
|
---|
[4790] | 9 | using System.ComponentModel;
|
---|
| 10 | using System.Linq.Expressions;
|
---|
[4787] | 11 |
|
---|
| 12 | namespace HeuristicLab.Core {
|
---|
| 13 | [StorableClass]
|
---|
| 14 | public sealed class ItemBinding : IItemBinding {
|
---|
| 15 | private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
---|
| 16 | [Storable]
|
---|
| 17 | private IDeepCloneable target;
|
---|
| 18 | [Storable]
|
---|
| 19 | private string targetPath;
|
---|
| 20 | [Storable]
|
---|
| 21 | private IDeepCloneable source;
|
---|
| 22 | [Storable]
|
---|
| 23 | private string sourcePath;
|
---|
| 24 | [Storable]
|
---|
| 25 | private bool bound;
|
---|
[4790] | 26 | [Storable]
|
---|
| 27 | private LambdaExpression bindingExpression;
|
---|
[4787] | 28 |
|
---|
[4790] | 29 | private Delegate bindingFunc;
|
---|
[4788] | 30 | private static object evhCacheLock = new object();
|
---|
| 31 | private static Dictionary<Type, DynamicMethod> eventHandlerCache = new Dictionary<Type, DynamicMethod>();
|
---|
[4787] | 32 |
|
---|
| 33 | [StorableConstructor]
|
---|
| 34 | private ItemBinding(bool deserializing) { }
|
---|
| 35 | private ItemBinding(ItemBinding original, Cloner cloner) {
|
---|
| 36 | this.target = cloner.Clone(original.target);
|
---|
| 37 | this.targetPath = original.targetPath;
|
---|
| 38 | this.source = cloner.Clone(original.source);
|
---|
| 39 | this.sourcePath = original.sourcePath;
|
---|
[4790] | 40 | this.bindingExpression = original.bindingExpression;
|
---|
| 41 | if (this.bindingExpression != null)
|
---|
| 42 | this.bindingFunc = this.bindingExpression.Compile();
|
---|
[4787] | 43 | if (original.bound) Bind();
|
---|
| 44 | }
|
---|
| 45 | public ItemBinding(IDeepCloneable target, string targetPath, IDeepCloneable source, string sourcePath) {
|
---|
| 46 | this.target = target;
|
---|
| 47 | this.targetPath = targetPath;
|
---|
| 48 | this.source = source;
|
---|
| 49 | this.sourcePath = sourcePath;
|
---|
| 50 | this.bound = false;
|
---|
| 51 | }
|
---|
[4790] | 52 | public ItemBinding(IDeepCloneable target, string targetPath, IDeepCloneable source, string sourcePath, LambdaExpression func)
|
---|
| 53 | : this(target, targetPath, source, sourcePath) {
|
---|
| 54 | this.bindingExpression = func;
|
---|
| 55 | this.bindingFunc = this.bindingExpression.Compile();
|
---|
| 56 | }
|
---|
[4787] | 57 |
|
---|
| 58 | public IDeepCloneable Clone(Cloner cloner) {
|
---|
| 59 | return new ItemBinding(this, cloner);
|
---|
| 60 | }
|
---|
| 61 |
|
---|
| 62 | public object Clone() {
|
---|
| 63 | return Clone(new Cloner());
|
---|
| 64 | }
|
---|
| 65 |
|
---|
| 66 | [StorableHook(HookType.AfterDeserialization)]
|
---|
| 67 | private void AfterDeserialization() {
|
---|
| 68 | if (bound) RegisterEventHandlers();
|
---|
[4790] | 69 | if (bindingExpression != null)
|
---|
| 70 | bindingFunc = bindingExpression.Compile();
|
---|
[4787] | 71 | }
|
---|
| 72 |
|
---|
| 73 | public void Bind() {
|
---|
| 74 | RegisterEventHandlers();
|
---|
| 75 | bound = true;
|
---|
| 76 | ExecuteBinding(null);
|
---|
| 77 | }
|
---|
| 78 |
|
---|
| 79 | public void Unbind() {
|
---|
| 80 | DeregisterEventHandlers();
|
---|
| 81 | bound = false;
|
---|
| 82 | }
|
---|
| 83 |
|
---|
| 84 | private void RegisterEventHandlers() {
|
---|
| 85 | string[] properties = sourcePath.Split('.');
|
---|
| 86 | object current = source;
|
---|
| 87 | foreach (string property in properties) {
|
---|
[4790] | 88 | TryHookToChangedEvent(current, property);
|
---|
[4787] | 89 | MethodInfo mInfo = current.GetType().GetProperty(property, Flags).GetGetMethod(true);
|
---|
| 90 | object next = mInfo.Invoke(current, null);
|
---|
| 91 | if (next == null) return;
|
---|
| 92 | current = next;
|
---|
| 93 | }
|
---|
| 94 | }
|
---|
| 95 |
|
---|
| 96 | private void DeregisterEventHandlers() {
|
---|
| 97 | string[] properties = sourcePath.Split('.');
|
---|
| 98 | object current = source;
|
---|
| 99 | foreach (string property in properties) {
|
---|
[4790] | 100 | TryUnhookFromChangedEvent(current, property);
|
---|
[4787] | 101 | MethodInfo mInfo = current.GetType().GetProperty(property, Flags).GetGetMethod(true);
|
---|
| 102 | object next = mInfo.Invoke(current, null);
|
---|
| 103 | if (next == null) return;
|
---|
| 104 | current = next;
|
---|
| 105 | }
|
---|
| 106 | }
|
---|
| 107 |
|
---|
| 108 | // DO NOT RENAME (string reference to this method name in CreateAndAddDynamicEventHandler)
|
---|
| 109 | private void ExecuteBinding(object sender) {
|
---|
| 110 | string[] sourceProperties = sourcePath.Split('.');
|
---|
| 111 | object cSource = source;
|
---|
[4790] | 112 | #region Navigate to source property (readd bindings if necessary)
|
---|
[4787] | 113 | bool reregister = false;
|
---|
| 114 | for (int i = 0; i < sourceProperties.Length; i++) {
|
---|
| 115 | if (cSource == null) return;
|
---|
| 116 | string property = sourceProperties[i];
|
---|
[4790] | 117 | if (reregister) TryHookToChangedEvent(cSource, property);
|
---|
[4787] | 118 | if (cSource == sender) reregister = true;
|
---|
[4790] | 119 | PropertyInfo pInfo = cSource.GetType().GetProperty(property, Flags);
|
---|
| 120 | if (pInfo == null) return;
|
---|
| 121 | object next = pInfo.GetGetMethod(true).Invoke(cSource, null);
|
---|
[4787] | 122 | cSource = next;
|
---|
| 123 | }
|
---|
[4788] | 124 | #endregion
|
---|
[4787] | 125 |
|
---|
| 126 | string[] targetProperties = targetPath.Split('.');
|
---|
| 127 | object cTarget = target;
|
---|
[4788] | 128 | MethodInfo targetSetter = null;
|
---|
| 129 | #region Navigate to target property
|
---|
[4787] | 130 | for (int i = 0; i < targetProperties.Length - 1; i++) {
|
---|
[4790] | 131 | PropertyInfo pInfo = cTarget.GetType().GetProperty(targetProperties[i], Flags);
|
---|
| 132 | if (pInfo == null) return;
|
---|
| 133 | cTarget = pInfo.GetGetMethod(true).Invoke(cTarget, null);
|
---|
[4787] | 134 | if (cTarget == null) return;
|
---|
| 135 | }
|
---|
[4788] | 136 | targetSetter = cTarget.GetType().GetProperty(targetProperties.Last(), Flags).GetSetMethod(true);
|
---|
| 137 | if (targetSetter == null) return;
|
---|
| 138 | #endregion
|
---|
| 139 |
|
---|
[4790] | 140 | if (bindingFunc != null) {
|
---|
| 141 | targetSetter.Invoke(cTarget, new object[] { bindingFunc.DynamicInvoke(new object[] { cSource }) });
|
---|
| 142 | } else if (cSource == null && !targetSetter.GetParameters().First().ParameterType.IsValueType
|
---|
[4788] | 143 | || cSource != null && cSource.GetType().IsValueType)
|
---|
| 144 | targetSetter.Invoke(cTarget, new object[] { cSource });
|
---|
[4787] | 145 | else if (cSource is ICloneable)
|
---|
[4788] | 146 | targetSetter.Invoke(cTarget, new object[] { ((ICloneable)cSource).Clone() });
|
---|
[4787] | 147 | else throw new InvalidOperationException("Can only bind to targets which are either a ValueType or an ICloneable.");
|
---|
| 148 | }
|
---|
| 149 |
|
---|
| 150 | private bool TryHookToChangedEvent(object obj, string property) {
|
---|
[4790] | 151 | INotifyPropertyChanged npc = (obj as INotifyPropertyChanged);
|
---|
| 152 | if (npc != null) {
|
---|
| 153 | npc.PropertyChanged += new PropertyChangedEventHandler(source_PropertyChanged);
|
---|
| 154 | return true;
|
---|
| 155 | } else {
|
---|
| 156 | EventInfo eInfo = obj.GetType().GetEvent(property + "Changed", Flags);
|
---|
| 157 | if (eInfo != null) {
|
---|
| 158 | Type[] methodParams = eInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray();
|
---|
| 159 | if (!eventHandlerCache.ContainsKey(methodParams[1]))
|
---|
| 160 | CreateAndAddDynamicEventHandler(eInfo, methodParams);
|
---|
| 161 | eInfo.AddEventHandler(obj, eventHandlerCache[methodParams[1]].CreateDelegate(eInfo.EventHandlerType, this));
|
---|
| 162 | }
|
---|
| 163 | return eInfo != null;
|
---|
[4787] | 164 | }
|
---|
| 165 | }
|
---|
| 166 |
|
---|
| 167 | private bool TryUnhookFromChangedEvent(object obj, string property) {
|
---|
[4790] | 168 | INotifyPropertyChanged npc = (obj as INotifyPropertyChanged);
|
---|
| 169 | if (npc != null) {
|
---|
| 170 | npc.PropertyChanged -= new PropertyChangedEventHandler(source_PropertyChanged);
|
---|
| 171 | return true;
|
---|
| 172 | } else {
|
---|
| 173 | EventInfo eInfo = obj.GetType().GetEvent(property, Flags);
|
---|
| 174 | if (eInfo != null) {
|
---|
| 175 | Type[] methodParams = eInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray();
|
---|
| 176 | if (eventHandlerCache.ContainsKey(methodParams[1]))
|
---|
| 177 | eInfo.RemoveEventHandler(obj, eventHandlerCache[methodParams[1]].CreateDelegate(eInfo.EventHandlerType, this));
|
---|
| 178 | }
|
---|
| 179 | return eInfo != null;
|
---|
[4787] | 180 | }
|
---|
| 181 | }
|
---|
| 182 |
|
---|
[4790] | 183 | private void source_PropertyChanged(object sender, PropertyChangedEventArgs e) {
|
---|
| 184 | ExecuteBinding(sender);
|
---|
| 185 | }
|
---|
| 186 |
|
---|
[4787] | 187 | private void CreateAndAddDynamicEventHandler(EventInfo eInfo, Type[] methodParams) {
|
---|
[4788] | 188 | lock (evhCacheLock) {
|
---|
| 189 | if (!eventHandlerCache.ContainsKey(methodParams[1])) {
|
---|
| 190 | Type[] instanceMethodParams = new Type[] { GetType() }.Concat(methodParams).ToArray();
|
---|
[4790] | 191 | DynamicMethod dynamicEventHandler = new DynamicMethod("source_Changed", null, instanceMethodParams, GetType());
|
---|
[4788] | 192 | MethodInfo executeBindingMethodInfo = GetType().GetMethod("ExecuteBinding", Flags);
|
---|
| 193 | ILGenerator iLG = dynamicEventHandler.GetILGenerator();
|
---|
| 194 | iLG.Emit(OpCodes.Ldarg_0); // load 'this' pointer
|
---|
| 195 | iLG.Emit(OpCodes.Ldarg_1); // load sender parameter
|
---|
| 196 | iLG.Emit(OpCodes.Call, executeBindingMethodInfo); // call method to execute the binding
|
---|
| 197 | iLG.Emit(OpCodes.Ret);
|
---|
| 198 | eventHandlerCache.Add(methodParams[1], dynamicEventHandler);
|
---|
| 199 | }
|
---|
| 200 | }
|
---|
[4787] | 201 | }
|
---|
| 202 | }
|
---|
| 203 | }
|
---|