source: branches/ParameterBinding/HeuristicLab.Core/3.3/ItemBinding.cs @ 4788

Last change on this file since 4788 was 4788, checked in by abeham, 12 years ago

#1258

  • Changed event handler cache to be static and keeping the DynamicMethod
  • Added case when source property was null
  • Added regions to clarify code a little
File size: 6.7 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
6using HeuristicLab.Common;
7using System.Reflection;
8using System.Reflection.Emit;
9
10namespace 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}
Note: See TracBrowser for help on using the repository browser.