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;
|
---|
9 | using System.ComponentModel;
|
---|
10 | using System.Linq.Expressions;
|
---|
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;
|
---|
26 | [Storable]
|
---|
27 | private LambdaExpression bindingExpression;
|
---|
28 |
|
---|
29 | private Delegate bindingFunc;
|
---|
30 | private static object evhCacheLock = new object();
|
---|
31 | private static Dictionary<Type, DynamicMethod> eventHandlerCache = new Dictionary<Type, DynamicMethod>();
|
---|
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;
|
---|
40 | this.bindingExpression = original.bindingExpression;
|
---|
41 | if (this.bindingExpression != null)
|
---|
42 | this.bindingFunc = this.bindingExpression.Compile();
|
---|
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 | }
|
---|
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 | }
|
---|
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();
|
---|
69 | if (bindingExpression != null)
|
---|
70 | bindingFunc = bindingExpression.Compile();
|
---|
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) {
|
---|
88 | TryHookToChangedEvent(current, property);
|
---|
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) {
|
---|
100 | TryUnhookFromChangedEvent(current, property);
|
---|
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;
|
---|
112 | #region Navigate to source property (readd bindings if necessary)
|
---|
113 | bool reregister = false;
|
---|
114 | for (int i = 0; i < sourceProperties.Length; i++) {
|
---|
115 | if (cSource == null) return;
|
---|
116 | string property = sourceProperties[i];
|
---|
117 | if (reregister) TryHookToChangedEvent(cSource, property);
|
---|
118 | if (cSource == sender) reregister = true;
|
---|
119 | PropertyInfo pInfo = cSource.GetType().GetProperty(property, Flags);
|
---|
120 | if (pInfo == null) return;
|
---|
121 | object next = pInfo.GetGetMethod(true).Invoke(cSource, null);
|
---|
122 | cSource = next;
|
---|
123 | }
|
---|
124 | #endregion
|
---|
125 |
|
---|
126 | string[] targetProperties = targetPath.Split('.');
|
---|
127 | object cTarget = target;
|
---|
128 | MethodInfo targetSetter = null;
|
---|
129 | #region Navigate to target property
|
---|
130 | for (int i = 0; i < targetProperties.Length - 1; i++) {
|
---|
131 | PropertyInfo pInfo = cTarget.GetType().GetProperty(targetProperties[i], Flags);
|
---|
132 | if (pInfo == null) return;
|
---|
133 | cTarget = pInfo.GetGetMethod(true).Invoke(cTarget, null);
|
---|
134 | if (cTarget == null) return;
|
---|
135 | }
|
---|
136 | targetSetter = cTarget.GetType().GetProperty(targetProperties.Last(), Flags).GetSetMethod(true);
|
---|
137 | if (targetSetter == null) return;
|
---|
138 | #endregion
|
---|
139 |
|
---|
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
|
---|
143 | || cSource != null && cSource.GetType().IsValueType)
|
---|
144 | targetSetter.Invoke(cTarget, new object[] { cSource });
|
---|
145 | else if (cSource is ICloneable)
|
---|
146 | targetSetter.Invoke(cTarget, new object[] { ((ICloneable)cSource).Clone() });
|
---|
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) {
|
---|
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;
|
---|
164 | }
|
---|
165 | }
|
---|
166 |
|
---|
167 | private bool TryUnhookFromChangedEvent(object obj, string property) {
|
---|
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;
|
---|
180 | }
|
---|
181 | }
|
---|
182 |
|
---|
183 | private void source_PropertyChanged(object sender, PropertyChangedEventArgs e) {
|
---|
184 | ExecuteBinding(sender);
|
---|
185 | }
|
---|
186 |
|
---|
187 | private void CreateAndAddDynamicEventHandler(EventInfo eInfo, Type[] methodParams) {
|
---|
188 | lock (evhCacheLock) {
|
---|
189 | if (!eventHandlerCache.ContainsKey(methodParams[1])) {
|
---|
190 | Type[] instanceMethodParams = new Type[] { GetType() }.Concat(methodParams).ToArray();
|
---|
191 | DynamicMethod dynamicEventHandler = new DynamicMethod("source_Changed", null, instanceMethodParams, GetType());
|
---|
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 | }
|
---|
201 | }
|
---|
202 | }
|
---|
203 | }
|
---|