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