Add ScriptUI to support XAML file (#320)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / XamlBinding / MessagingCenter.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Reflection;
5
6 namespace Tizen.NUI.Binding
7 {
8     internal interface IMessagingCenter
9     {
10         void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class;
11
12         void Send<TSender>(TSender sender, string message) where TSender : class;
13
14         void Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null) where TSender : class;
15
16         void Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source = null) where TSender : class;
17
18         void Unsubscribe<TSender, TArgs>(object subscriber, string message) where TSender : class;
19
20         void Unsubscribe<TSender>(object subscriber, string message) where TSender : class;
21     }
22
23     internal class MessagingCenter : IMessagingCenter
24     {
25         public static IMessagingCenter Instance { get; } = new MessagingCenter();
26
27         class Sender : Tuple<string, Type, Type>
28         {
29             public Sender(string message, Type senderType, Type argType) : base(message, senderType, argType)
30             {
31             }
32         }
33
34         delegate bool Filter(object sender);
35
36         class MaybeWeakReference
37         {
38             WeakReference DelegateWeakReference { get; }
39             object DelegateStrongReference { get; }
40
41             readonly bool _isStrongReference;
42
43             public MaybeWeakReference(object subscriber, object delegateSource)
44             {
45                 if (subscriber.Equals(delegateSource))
46                 {
47                     // The target is the subscriber; we can use a weakreference
48                     DelegateWeakReference = new WeakReference(delegateSource);
49                     _isStrongReference = false;
50                 }
51                 else
52                 {
53                     DelegateStrongReference = delegateSource;
54                     _isStrongReference = true;
55                 }
56             }
57
58             public object Target => _isStrongReference ? DelegateStrongReference : DelegateWeakReference.Target;
59             public bool IsAlive => _isStrongReference || DelegateWeakReference.IsAlive;
60         }
61
62         class Subscription : Tuple<WeakReference, MaybeWeakReference, MethodInfo, Filter>
63         {
64             public Subscription(object subscriber, object delegateSource, MethodInfo methodInfo, Filter filter)
65                 : base(new WeakReference(subscriber), new MaybeWeakReference(subscriber, delegateSource), methodInfo, filter)
66             {
67             }
68
69             public WeakReference Subscriber => Item1;
70             MaybeWeakReference DelegateSource => Item2;
71             MethodInfo MethodInfo => Item3;
72             Filter Filter => Item4;
73
74             public void InvokeCallback(object sender, object args)
75             {
76                 if (!Filter(sender))
77                 {
78                     return;
79                 }
80
81                 if (MethodInfo.IsStatic)
82                 {
83                     MethodInfo.Invoke(null, MethodInfo.GetParameters().Length == 1 ? new[] { sender } : new[] { sender, args });
84                     return;
85                 }
86
87                 var target = DelegateSource.Target;
88
89                 if (target == null)
90                 {
91                     return; // Collected 
92                 }
93
94                 MethodInfo.Invoke(target, MethodInfo.GetParameters().Length == 1 ? new[] { sender } : new[] { sender, args });
95             }
96
97             public bool CanBeRemoved()
98             {
99                 return !Subscriber.IsAlive || !DelegateSource.IsAlive;
100             }
101         }
102
103         readonly Dictionary<Sender, List<Subscription>> _subscriptions =
104             new Dictionary<Sender, List<Subscription>>();
105
106         public static void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class
107         {
108             Instance.Send(sender, message, args);
109         }
110
111         void IMessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args)
112         {
113             if (sender == null)
114                 throw new ArgumentNullException(nameof(sender));
115             InnerSend(message, typeof(TSender), typeof(TArgs), sender, args);
116         }
117
118         public static void Send<TSender>(TSender sender, string message) where TSender : class
119         {
120             Instance.Send(sender, message);
121         }
122
123         void IMessagingCenter.Send<TSender>(TSender sender, string message)
124         {
125             if (sender == null)
126                 throw new ArgumentNullException(nameof(sender));
127             InnerSend(message, typeof(TSender), null, sender, null);
128         }
129
130         public static void Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null) where TSender : class
131         {
132             Instance.Subscribe(subscriber, message, callback, source);
133         }
134
135         void IMessagingCenter.Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source)
136         {
137             if (subscriber == null)
138                 throw new ArgumentNullException(nameof(subscriber));
139             if (callback == null)
140                 throw new ArgumentNullException(nameof(callback));
141
142             var target = callback.Target;
143
144             Filter filter = sender =>
145             {
146                 var send = (TSender)sender;
147                 return (source == null || send == source);
148             };
149
150             InnerSubscribe(subscriber, message, typeof(TSender), typeof(TArgs), target, callback.GetMethodInfo(), filter);
151         }
152
153         public static void Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source = null) where TSender : class
154         {
155             Instance.Subscribe(subscriber, message, callback, source);
156         }
157
158         void IMessagingCenter.Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source)
159         {
160             if (subscriber == null)
161                 throw new ArgumentNullException(nameof(subscriber));
162             if (callback == null)
163                 throw new ArgumentNullException(nameof(callback));
164
165             var target = callback.Target;
166
167             Filter filter = sender =>
168             {
169                 var send = (TSender)sender;
170                 return (source == null || send == source);
171             };
172
173             InnerSubscribe(subscriber, message, typeof(TSender), null, target, callback.GetMethodInfo(), filter);
174         }
175
176         public static void Unsubscribe<TSender, TArgs>(object subscriber, string message) where TSender : class
177         {
178             Instance.Unsubscribe<TSender, TArgs>(subscriber, message);
179         }
180
181         void IMessagingCenter.Unsubscribe<TSender, TArgs>(object subscriber, string message)
182         {
183             InnerUnsubscribe(message, typeof(TSender), typeof(TArgs), subscriber);
184         }
185
186         public static void Unsubscribe<TSender>(object subscriber, string message) where TSender : class
187         {
188             Instance.Unsubscribe<TSender>(subscriber, message);
189         }
190
191         void IMessagingCenter.Unsubscribe<TSender>(object subscriber, string message)
192         {
193             InnerUnsubscribe(message, typeof(TSender), null, subscriber);
194         }
195
196         void InnerSend(string message, Type senderType, Type argType, object sender, object args)
197         {
198             if (message == null)
199                 throw new ArgumentNullException(nameof(message));
200             var key = new Sender(message, senderType, argType);
201             if (!_subscriptions.ContainsKey(key))
202                 return;
203             List<Subscription> subcriptions = _subscriptions[key];
204             if (subcriptions == null || !subcriptions.Any())
205                 return; // should not be reachable
206
207             // ok so this code looks a bit funky but here is the gist of the problem. It is possible that in the course
208             // of executing the callbacks for this message someone will subscribe/unsubscribe from the same message in
209             // the callback. This would invalidate the enumerator. To work around this we make a copy. However if you unsubscribe 
210             // from a message you can fairly reasonably expect that you will therefor not receive a call. To fix this we then
211             // check that the item we are about to send the message to actually exists in the live list.
212             List<Subscription> subscriptionsCopy = subcriptions.ToList();
213             foreach (Subscription subscription in subscriptionsCopy)
214             {
215                 if (subscription.Subscriber.Target != null && subcriptions.Contains(subscription))
216                 {
217                     subscription.InvokeCallback(sender, args);
218                 }
219             }
220         }
221
222         void InnerSubscribe(object subscriber, string message, Type senderType, Type argType, object target, MethodInfo methodInfo, Filter filter)
223         {
224             if (message == null)
225                 throw new ArgumentNullException(nameof(message));
226             var key = new Sender(message, senderType, argType);
227             var value = new Subscription(subscriber, target, methodInfo, filter);
228             if (_subscriptions.ContainsKey(key))
229             {
230                 _subscriptions[key].Add(value);
231             }
232             else
233             {
234                 var list = new List<Subscription> { value };
235                 _subscriptions[key] = list;
236             }
237         }
238
239         void InnerUnsubscribe(string message, Type senderType, Type argType, object subscriber)
240         {
241             if (subscriber == null)
242                 throw new ArgumentNullException(nameof(subscriber));
243             if (message == null)
244                 throw new ArgumentNullException(nameof(message));
245
246             var key = new Sender(message, senderType, argType);
247             if (!_subscriptions.ContainsKey(key))
248                 return;
249             _subscriptions[key].RemoveAll(sub => sub.CanBeRemoved() || sub.Subscriber.Target == subscriber);
250             if (!_subscriptions[key].Any())
251                 _subscriptions.Remove(key);
252         }
253
254         // This is a bit gross; it only exists to support the unit tests in PageTests
255         // because the implementations of ActionSheet, Alert, and IsBusy are all very
256         // tightly coupled to the MessagingCenter singleton 
257         internal static void ClearSubscribers()
258         {
259             (Instance as MessagingCenter)?._subscriptions.Clear();
260         }
261     }
262 }