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