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