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 |
|
---|
10 | namespace HeuristicLab.Core {
|
---|
11 | [StorableClass]
|
---|
12 | public sealed class ItemBinding : IItemBinding {
|
---|
13 | private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
---|
14 | [Storable]
|
---|
15 | private IDeepCloneable target;
|
---|
16 | [Storable]
|
---|
17 | private string targetPath;
|
---|
18 | [Storable]
|
---|
19 | private IDeepCloneable source;
|
---|
20 | [Storable]
|
---|
21 | private string sourcePath;
|
---|
22 | [Storable]
|
---|
23 | private bool bound;
|
---|
24 |
|
---|
25 | private static object evhCacheLock = new object();
|
---|
26 | private static Dictionary<Type, DynamicMethod> eventHandlerCache = new Dictionary<Type, DynamicMethod>();
|
---|
27 |
|
---|
28 | [StorableConstructor]
|
---|
29 | private ItemBinding(bool deserializing) { }
|
---|
30 | private ItemBinding(ItemBinding original, Cloner cloner) {
|
---|
31 | this.target = cloner.Clone(original.target);
|
---|
32 | this.targetPath = original.targetPath;
|
---|
33 | this.source = cloner.Clone(original.source);
|
---|
34 | this.sourcePath = original.sourcePath;
|
---|
35 | if (original.bound) Bind();
|
---|
36 | }
|
---|
37 | public ItemBinding(IDeepCloneable target, string targetPath, IDeepCloneable source, string sourcePath) {
|
---|
38 | this.target = target;
|
---|
39 | this.targetPath = targetPath;
|
---|
40 | this.source = source;
|
---|
41 | this.sourcePath = sourcePath;
|
---|
42 | this.bound = false;
|
---|
43 | }
|
---|
44 |
|
---|
45 | public IDeepCloneable Clone(Cloner cloner) {
|
---|
46 | return new ItemBinding(this, cloner);
|
---|
47 | }
|
---|
48 |
|
---|
49 | public object Clone() {
|
---|
50 | return Clone(new Cloner());
|
---|
51 | }
|
---|
52 |
|
---|
53 | [StorableHook(HookType.AfterDeserialization)]
|
---|
54 | private void AfterDeserialization() {
|
---|
55 | if (bound) RegisterEventHandlers();
|
---|
56 | }
|
---|
57 |
|
---|
58 | public void Bind() {
|
---|
59 | RegisterEventHandlers();
|
---|
60 | bound = true;
|
---|
61 | ExecuteBinding(null);
|
---|
62 | }
|
---|
63 |
|
---|
64 | public void Unbind() {
|
---|
65 | DeregisterEventHandlers();
|
---|
66 | bound = false;
|
---|
67 | }
|
---|
68 |
|
---|
69 | private void RegisterEventHandlers() {
|
---|
70 | string[] properties = sourcePath.Split('.');
|
---|
71 | object current = source;
|
---|
72 | foreach (string property in properties) {
|
---|
73 | TryHookToChangedEvent(current, property + "Changed");
|
---|
74 | MethodInfo mInfo = current.GetType().GetProperty(property, Flags).GetGetMethod(true);
|
---|
75 | object next = mInfo.Invoke(current, null);
|
---|
76 | if (next == null) return;
|
---|
77 | current = next;
|
---|
78 | }
|
---|
79 | }
|
---|
80 |
|
---|
81 | private void DeregisterEventHandlers() {
|
---|
82 | string[] properties = sourcePath.Split('.');
|
---|
83 | object current = source;
|
---|
84 | foreach (string property in properties) {
|
---|
85 | TryUnhookFromChangedEvent(current, property + "Changed");
|
---|
86 | MethodInfo mInfo = current.GetType().GetProperty(property, Flags).GetGetMethod(true);
|
---|
87 | object next = mInfo.Invoke(current, null);
|
---|
88 | if (next == null) return;
|
---|
89 | current = next;
|
---|
90 | }
|
---|
91 | }
|
---|
92 |
|
---|
93 | // DO NOT RENAME (string reference to this method name in CreateAndAddDynamicEventHandler)
|
---|
94 | private void ExecuteBinding(object sender) {
|
---|
95 | string[] sourceProperties = sourcePath.Split('.');
|
---|
96 | object cSource = source;
|
---|
97 | #region Navigate to source property
|
---|
98 | bool reregister = false;
|
---|
99 | for (int i = 0; i < sourceProperties.Length; i++) {
|
---|
100 | if (cSource == null) return;
|
---|
101 | string property = sourceProperties[i];
|
---|
102 | if (reregister) TryHookToChangedEvent(cSource, property + "Changed");
|
---|
103 | if (cSource == sender) reregister = true;
|
---|
104 | object next = cSource.GetType().GetProperty(property, Flags).GetGetMethod(true).Invoke(cSource, null);
|
---|
105 | cSource = next;
|
---|
106 | }
|
---|
107 | #endregion
|
---|
108 |
|
---|
109 | string[] targetProperties = targetPath.Split('.');
|
---|
110 | object cTarget = target;
|
---|
111 | MethodInfo targetSetter = null;
|
---|
112 | #region Navigate to target property
|
---|
113 | for (int i = 0; i < targetProperties.Length - 1; i++) {
|
---|
114 | cTarget = cTarget.GetType().GetProperty(targetProperties[i], Flags).GetGetMethod(true).Invoke(cTarget, null);
|
---|
115 | if (cTarget == null) return;
|
---|
116 | }
|
---|
117 | targetSetter = cTarget.GetType().GetProperty(targetProperties.Last(), Flags).GetSetMethod(true);
|
---|
118 | if (targetSetter == null) return;
|
---|
119 | #endregion
|
---|
120 |
|
---|
121 | if (cSource == null && !targetSetter.GetParameters().First().ParameterType.IsValueType
|
---|
122 | || cSource != null && cSource.GetType().IsValueType)
|
---|
123 | targetSetter.Invoke(cTarget, new object[] { cSource });
|
---|
124 | else if (cSource is ICloneable)
|
---|
125 | targetSetter.Invoke(cTarget, new object[] { ((ICloneable)cSource).Clone() });
|
---|
126 | else throw new InvalidOperationException("Can only bind to targets which are either a ValueType or an ICloneable.");
|
---|
127 | }
|
---|
128 |
|
---|
129 | private bool TryHookToChangedEvent(object obj, string property) {
|
---|
130 | EventInfo eInfo = obj.GetType().GetEvent(property, Flags);
|
---|
131 | if (eInfo != null) {
|
---|
132 | Type[] methodParams = eInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray();
|
---|
133 | if (!eventHandlerCache.ContainsKey(methodParams[1]))
|
---|
134 | CreateAndAddDynamicEventHandler(eInfo, methodParams);
|
---|
135 | eInfo.AddEventHandler(obj, eventHandlerCache[methodParams[1]].CreateDelegate(eInfo.EventHandlerType, this));
|
---|
136 | }
|
---|
137 | return eInfo != null;
|
---|
138 | }
|
---|
139 |
|
---|
140 | private bool TryUnhookFromChangedEvent(object obj, string property) {
|
---|
141 | EventInfo eInfo = obj.GetType().GetEvent(property, Flags);
|
---|
142 | if (eInfo != null) {
|
---|
143 | Type[] methodParams = eInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray();
|
---|
144 | if (eventHandlerCache.ContainsKey(methodParams[1]))
|
---|
145 | eInfo.RemoveEventHandler(obj, eventHandlerCache[methodParams[1]].CreateDelegate(eInfo.EventHandlerType, this));
|
---|
146 | }
|
---|
147 | return eInfo != null;
|
---|
148 | }
|
---|
149 |
|
---|
150 | private void CreateAndAddDynamicEventHandler(EventInfo eInfo, Type[] methodParams) {
|
---|
151 | lock (evhCacheLock) {
|
---|
152 | if (!eventHandlerCache.ContainsKey(methodParams[1])) {
|
---|
153 | Type[] instanceMethodParams = new Type[] { GetType() }.Concat(methodParams).ToArray();
|
---|
154 | DynamicMethod dynamicEventHandler = new DynamicMethod("sourceChanged", null, instanceMethodParams, GetType());
|
---|
155 | MethodInfo executeBindingMethodInfo = GetType().GetMethod("ExecuteBinding", Flags);
|
---|
156 | ILGenerator iLG = dynamicEventHandler.GetILGenerator();
|
---|
157 | iLG.Emit(OpCodes.Ldarg_0); // load 'this' pointer
|
---|
158 | iLG.Emit(OpCodes.Ldarg_1); // load sender parameter
|
---|
159 | iLG.Emit(OpCodes.Call, executeBindingMethodInfo); // call method to execute the binding
|
---|
160 | iLG.Emit(OpCodes.Ret);
|
---|
161 | eventHandlerCache.Add(methodParams[1], dynamicEventHandler);
|
---|
162 | }
|
---|
163 | }
|
---|
164 | }
|
---|
165 | }
|
---|
166 | }
|
---|