Follow formatting NUI
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / XamlBinding / TypedBinding.cs
1 #define DO_NOT_CHECK_FOR_BINDING_REUSE
2
3 using System;
4 using System.ComponentModel;
5 using System.Globalization;
6 using System.Collections.Generic;
7 using Tizen.NUI.Binding;
8
9 namespace Tizen.NUI.Binding.Internals
10 {
11     //FIXME: need a better name for this, and share with Binding, so we can share more unittests
12     /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
13     [EditorBrowsable(EditorBrowsableState.Never)]
14     public abstract class TypedBindingBase : BindingBase
15     {
16         IValueConverter _converter;
17         object _converterParameter;
18         object _source;
19         string _updateSourceEventName;
20
21         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
22         [EditorBrowsable(EditorBrowsableState.Never)]
23         public IValueConverter Converter
24         {
25             get { return _converter; }
26             set
27             {
28                 ThrowIfApplied();
29                 _converter = value;
30             }
31         }
32
33         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
34         [EditorBrowsable(EditorBrowsableState.Never)]
35         public object ConverterParameter
36         {
37             get { return _converterParameter; }
38             set
39             {
40                 ThrowIfApplied();
41                 _converterParameter = value;
42             }
43         }
44
45         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
46         [EditorBrowsable(EditorBrowsableState.Never)]
47         public object Source
48         {
49             get { return _source; }
50             set
51             {
52                 ThrowIfApplied();
53                 _source = value;
54             }
55         }
56
57         internal string UpdateSourceEventName
58         {
59             get { return _updateSourceEventName; }
60             set
61             {
62                 ThrowIfApplied();
63                 _updateSourceEventName = value;
64             }
65         }
66
67         internal TypedBindingBase()
68         {
69         }
70     }
71
72     internal sealed class TypedBinding<TSource, TProperty> : TypedBindingBase
73     {
74         readonly Func<TSource, TProperty> _getter;
75         readonly Action<TSource, TProperty> _setter;
76         readonly PropertyChangedProxy[] _handlers;
77
78         public TypedBinding(Func<TSource, TProperty> getter, Action<TSource, TProperty> setter, Tuple<Func<TSource, object>, string>[] handlers)
79         {
80             if (getter == null)
81                 throw new ArgumentNullException(nameof(getter));
82
83             _getter = getter;
84             _setter = setter;
85
86             if (handlers == null)
87                 return;
88
89             _handlers = new PropertyChangedProxy[handlers.Length];
90             for (var i = 0; i < handlers.Length; i++)
91                 _handlers[i] = new PropertyChangedProxy(handlers[i].Item1, handlers[i].Item2, this);
92         }
93
94         readonly WeakReference<object> _weakSource = new WeakReference<object>(null);
95         readonly WeakReference<BindableObject> _weakTarget = new WeakReference<BindableObject>(null);
96         BindableProperty _targetProperty;
97
98         // Applies the binding to a previously set source and target.
99         internal override void Apply(bool fromTarget = false)
100         {
101             base.Apply(fromTarget);
102
103             BindableObject target;
104 #if DO_NOT_CHECK_FOR_BINDING_REUSE
105             if (!_weakTarget.TryGetTarget(out target))
106                 throw new InvalidOperationException();
107 #else
108             if (!_weakTarget.TryGetTarget(out target) || target == null) {
109                 Unapply();
110                 return;
111             }
112 #endif
113             object source;
114             if (_weakSource.TryGetTarget(out source) && source != null)
115                 ApplyCore(source, target, _targetProperty, fromTarget);
116         }
117
118         // Applies the binding to a new source or target.
119         internal override void Apply(object context, BindableObject bindObj, BindableProperty targetProperty, bool fromBindingContextChanged = false)
120         {
121             _targetProperty = targetProperty;
122             var source = Source ?? Context ?? context;
123             var isApplied = IsApplied;
124
125             if (Source != null && isApplied && fromBindingContextChanged)
126                 return;
127
128             base.Apply(source, bindObj, targetProperty, fromBindingContextChanged);
129
130 #if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
131             BindableObject prevTarget;
132             if (_weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, bindObj))
133                 throw new InvalidOperationException("Binding instances can not be reused");
134
135             object previousSource;
136             if (_weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, source))
137                 throw new InvalidOperationException("Binding instances can not be reused");
138 #endif
139             _weakSource.SetTarget(source);
140             _weakTarget.SetTarget(bindObj);
141
142             ApplyCore(source, bindObj, targetProperty);
143         }
144
145         internal override BindingBase Clone()
146         {
147             Tuple<Func<TSource, object>, string>[] handlers = _handlers == null ? null : new Tuple<Func<TSource, object>, string>[_handlers.Length];
148             if (handlers != null)
149             {
150                 for (var i = 0; i < _handlers.Length; i++)
151                     handlers[i] = new Tuple<Func<TSource, object>, string>(_handlers[i].PartGetter, _handlers[i].PropertyName);
152             }
153             return new TypedBinding<TSource, TProperty>(_getter, _setter, handlers)
154             {
155                 Mode = Mode,
156                 Converter = Converter,
157                 ConverterParameter = ConverterParameter,
158                 StringFormat = StringFormat,
159                 Source = Source,
160                 UpdateSourceEventName = UpdateSourceEventName,
161             };
162         }
163
164         internal override object GetSourceValue(object value, Type targetPropertyType)
165         {
166             if (Converter != null)
167                 value = Converter.Convert(value, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
168
169             //return base.GetSourceValue(value, targetPropertyType);
170             if (StringFormat != null)
171                 return string.Format(StringFormat, value);
172
173             return value;
174         }
175
176         internal override object GetTargetValue(object value, Type sourcePropertyType)
177         {
178             if (Converter != null)
179                 value = Converter.ConvertBack(value, sourcePropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
180
181             //return base.GetTargetValue(value, sourcePropertyType);
182             return value;
183         }
184
185         internal override void Unapply(bool fromBindingContextChanged = false)
186         {
187             if (Source != null && fromBindingContextChanged && IsApplied)
188                 return;
189
190 #if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
191             base.Unapply(fromBindingContextChanged:fromBindingContextChanged);
192 #endif
193             if (_handlers != null)
194                 Unsubscribe();
195
196 #if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
197             _weakSource.SetTarget(null);
198             _weakTarget.SetTarget(null);
199 #endif
200         }
201
202         // ApplyCore is as slim as it should be:
203         // Setting  100000 values                                               : 17ms.
204         // ApplyCore  100000 (w/o INPC, w/o unnapply)   : 20ms.
205         internal void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false)
206         {
207             var isTSource = sourceObject != null && sourceObject is TSource;
208             var mode = this.GetRealizedMode(property);
209             if ((mode == BindingMode.OneWay || mode == BindingMode.OneTime) && fromTarget)
210                 return;
211
212             var needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay || mode == BindingMode.OneTime;
213
214             if (isTSource && (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) && _handlers != null)
215                 Subscribe((TSource)sourceObject);
216
217             if (needsGetter)
218             {
219                 var value = property.DefaultValue;
220                 if (isTSource)
221                 {
222                     try
223                     {
224                         value = GetSourceValue(_getter((TSource)sourceObject), property.ReturnType);
225                     }
226                     catch (Exception ex) when (ex is NullReferenceException || ex is KeyNotFoundException)
227                     {
228                     }
229                 }
230                 if (!TryConvert(ref value, property, property.ReturnType, true))
231                 {
232                     // Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType);
233                     return;
234                 }
235                 target.SetValueCore(property, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted, false);
236                 return;
237             }
238
239             var needsSetter = (mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource;
240             if (needsSetter && _setter != null && isTSource)
241             {
242                 var value = GetTargetValue(target.GetValue(property), typeof(TProperty));
243                 if (!TryConvert(ref value, property, typeof(TProperty), false))
244                 {
245                     // Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, typeof(TProperty));
246                     return;
247                 }
248                 _setter((TSource)sourceObject, (TProperty)value);
249             }
250         }
251
252         static bool TryConvert(ref object value, BindableProperty targetProperty, Type convertTo, bool toTarget)
253         {
254             if (value == null)
255                 return true;
256             if ((toTarget && targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
257                 return true;
258
259             object original = value;
260             try
261             {
262                 value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
263                 return true;
264             }
265             catch (Exception ex) when (ex is InvalidCastException || ex is FormatException || ex is OverflowException)
266             {
267                 value = original;
268                 return false;
269             }
270         }
271
272         class PropertyChangedProxy
273         {
274             public Func<TSource, object> PartGetter { get; }
275             public string PropertyName { get; }
276             public BindingExpression.WeakPropertyChangedProxy Listener { get; }
277             WeakReference<INotifyPropertyChanged> _weakPart = new WeakReference<INotifyPropertyChanged>(null);
278             readonly BindingBase _binding;
279
280             public INotifyPropertyChanged Part
281             {
282                 get
283                 {
284                     INotifyPropertyChanged target;
285                     if (_weakPart.TryGetTarget(out target))
286                         return target;
287                     return null;
288                 }
289                 set
290                 {
291                     _weakPart.SetTarget(value);
292                     Listener.SubscribeTo(value, OnPropertyChanged);
293                 }
294             }
295
296             public PropertyChangedProxy(Func<TSource, object> partGetter, string propertyName, BindingBase binding)
297             {
298                 PartGetter = partGetter;
299                 PropertyName = propertyName;
300                 _binding = binding;
301                 Listener = new BindingExpression.WeakPropertyChangedProxy();
302             }
303
304             void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
305             {
306                 if (!string.IsNullOrEmpty(e.PropertyName) && string.CompareOrdinal(e.PropertyName, PropertyName) != 0)
307                     return;
308                 Device.BeginInvokeOnMainThread(() => _binding.Apply(false));
309             }
310         }
311
312         void Subscribe(TSource sourceObject)
313         {
314             for (var i = 0; i < _handlers.Length; i++)
315             {
316                 var part = _handlers[i].PartGetter(sourceObject);
317                 if (part == null)
318                     break;
319                 var inpc = part as INotifyPropertyChanged;
320                 if (inpc == null)
321                     continue;
322                 _handlers[i].Part = (inpc);
323             }
324         }
325
326         void Unsubscribe()
327         {
328             for (var i = 0; i < _handlers.Length; i++)
329                 _handlers[i].Listener.Unsubscribe();
330         }
331     }
332 }