Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 4787 was 4787, checked in by abeham, 13 years ago

#1258

  • Updated binding according to discussion
  • Added small test case for the TSP
File size: 6.2 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 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}
Note: See TracBrowser for help on using the repository browser.