[NUI] Fix some SVACE issues.
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / XamlBinding / BindingExpression.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 using System;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.Globalization;
22 using System.Linq;
23 using System.Reflection;
24 using Tizen.NUI.Binding.Internals;
25 using System.Runtime.CompilerServices;
26
27 namespace Tizen.NUI.Binding
28 {
29     internal class BindingExpression
30     {
31         internal const string PropertyNotFoundErrorMessage = "'{0}' property not found on '{1}', target property: '{2}.{3}'";
32
33         readonly List<BindingExpressionPart> _parts = new List<BindingExpressionPart>();
34
35         BindableProperty _targetProperty;
36         WeakReference<object> _weakSource;
37         WeakReference<BindableObject> _weakTarget;
38
39         internal BindingExpression(BindingBase binding, string path)
40         {
41             if (binding == null)
42                 throw new ArgumentNullException(nameof(binding));
43             if (path == null)
44                 throw new ArgumentNullException(nameof(path));
45
46             Binding = binding;
47             Path = path;
48
49             ParsePath();
50         }
51
52         internal BindingBase Binding { get; }
53
54         internal string Path { get; }
55
56         /// <summary>
57         ///     Applies the binding expression to a previously set source and target.
58         /// </summary>
59         internal void Apply(bool fromTarget = false)
60         {
61             if (_weakSource == null || _weakTarget == null)
62                 return;
63
64             BindableObject target;
65             if (!_weakTarget.TryGetTarget(out target))
66             {
67                 Unapply();
68                 return;
69             }
70
71             object source;
72             if (_weakSource.TryGetTarget(out source) && _targetProperty != null)
73                 ApplyCore(source, target, _targetProperty, fromTarget);
74         }
75
76         /// <summary>
77         ///     Applies the binding expression to a new source or target.
78         /// </summary>
79         internal void Apply(object sourceObject, BindableObject target, BindableProperty property)
80         {
81             _targetProperty = property;
82
83             BindableObject prevTarget;
84             if (_weakTarget != null && _weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, target))
85                 throw new InvalidOperationException("Binding instances can not be reused");
86
87             object previousSource;
88             if (_weakSource != null && _weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, sourceObject))
89                 throw new InvalidOperationException("Binding instances can not be reused");
90
91             _weakSource = new WeakReference<object>(sourceObject);
92             _weakTarget = new WeakReference<BindableObject>(target);
93
94             ApplyCore(sourceObject, target, property);
95         }
96
97         internal void Unapply()
98         {
99             object sourceObject;
100             if (_weakSource != null && _weakSource.TryGetTarget(out sourceObject))
101             {
102                 for (var i = 0; i < _parts.Count; i++)
103                 {
104                     if (i + 1 == _parts.Count) //do not handle the last.
105                         break;
106
107                     BindingExpressionPart part = _parts[i];
108
109                     if (!part.IsSelf)
110                     {
111                         part.TryGetValue(sourceObject, out sourceObject);
112                     }
113
114                     part.Unsubscribe();
115                 }
116             }
117
118             _weakSource = null;
119             _weakTarget = null;
120         }
121
122         /// <summary>
123         ///     Applies the binding expression to a previously set source or target.
124         /// </summary>
125         void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false)
126         {
127             BindingMode mode = Binding.GetRealizedMode(_targetProperty);
128             if ((mode == BindingMode.OneWay || mode == BindingMode.OneTime) && fromTarget)
129                 return;
130
131             bool needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay || mode == BindingMode.OneTime;
132             bool needsSetter = !needsGetter && ((mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource);
133
134             object current = sourceObject;
135             object previous = null;
136             BindingExpressionPart part = null;
137
138             for (var i = 0; i < _parts.Count; i++)
139             {
140                 part = _parts[i];
141                 bool isLast = i + 1 == _parts.Count;
142
143                 if (!part.IsSelf && current != null)
144                 {
145                     // Allow the object instance itself to provide its own TypeInfo 
146                     var reflectable = current as IReflectableType;
147                     System.Reflection.TypeInfo currentType = reflectable != null ? reflectable.GetTypeInfo() : current.GetType().GetTypeInfo();
148                     if (part.LastGetter == null || !part.LastGetter.DeclaringType.GetTypeInfo().IsAssignableFrom(currentType))
149                         SetupPart(currentType, part);
150
151                     if (!isLast)
152                         part.TryGetValue(current, out current);
153                 }
154
155                 if (!part.IsSelf && current != null)
156                 {
157                     if ((needsGetter && part.LastGetter == null) || (needsSetter && part.NextPart == null && part.LastSetter == null))
158                     {
159                         Console.WriteLine("Binding, " + PropertyNotFoundErrorMessage, part.Content, current, target.GetType(), property.PropertyName);
160                         break;
161                     }
162                 }
163
164                 if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay)
165                 {
166                     var inpc = current as INotifyPropertyChanged;
167                     if (inpc != null && !ReferenceEquals(current, previous))
168                         part.Subscribe(inpc);
169                 }
170
171                 previous = current;
172             }
173
174             Debug.Assert(part != null, "There should always be at least the self part in the expression.");
175
176             if (needsGetter)
177             {
178                 object value = property.DefaultValue;
179                 if (part.TryGetValue(current, out value) || part.IsSelf)
180                 {
181                     value = Binding.GetSourceValue(value, property.ReturnType);
182                 }
183                 else
184                     value = property.DefaultValue;
185
186                 if (!TryConvert(ref value, property.ReturnType, true))
187                 {
188                     Console.WriteLine($"Binding : {value} can not be converted to type {property.ReturnType}");
189                     return;
190                 }
191
192                 target.SetValueCore(property, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted);
193             }
194             else if (needsSetter && part.LastSetter != null && current != null)
195             {
196                 object value = Binding.GetTargetValue(target.GetValue(property), part.SetterType);
197
198                 if (!TryConvert(ref value, part.SetterType, false))
199                 {
200                     Console.WriteLine($"Binding : {value} can not be converted to type {part.SetterType}");
201                     return;
202                 }
203
204                 object[] args;
205                 if (part.IsIndexer)
206                 {
207                     args = new object[part.Arguments.Length + 1];
208                     part.Arguments.CopyTo(args, 0);
209                     args[args.Length - 1] = value;
210                 }
211                 else if (part.IsBindablePropertySetter)
212                 {
213                     args = new[] { part.BindablePropertyField, value };
214                 }
215                 else
216                 {
217                     args = new[] { value };
218                 }
219
220                 part.LastSetter.Invoke(current, args);
221             }
222         }
223
224         IEnumerable<BindingExpressionPart> GetPart(string part)
225         {
226             part = part.Trim();
227             if (string.IsNullOrEmpty(part))
228                 throw new FormatException("Path contains an empty part");
229
230             BindingExpressionPart indexer = null;
231
232             int lbIndex = part.IndexOf('[');
233             if (lbIndex != -1)
234             {
235                 int rbIndex = part.LastIndexOf(']');
236                 if (rbIndex == -1)
237                     throw new FormatException("Indexer did not contain closing bracket");
238
239                 int argLength = rbIndex - lbIndex - 1;
240                 if (argLength == 0)
241                     throw new FormatException("Indexer did not contain arguments");
242
243                 string argString = part.Substring(lbIndex + 1, argLength);
244                 indexer = new BindingExpressionPart(this, argString, true);
245
246                 part = part.Substring(0, lbIndex);
247                 part = part.Trim();
248             }
249
250             if (part.Length > 0)
251                 yield return new BindingExpressionPart(this, part);
252             if (indexer != null)
253                 yield return indexer;
254         }
255
256         void ParsePath()
257         {
258             string p = Path.Trim();
259
260             var last = new BindingExpressionPart(this, ".");
261             _parts.Add(last);
262
263             if (p[0] == '.')
264             {
265                 if (p.Length == 1)
266                     return;
267
268                 p = p.Substring(1);
269             }
270
271             string[] pathParts = p.Split('.');
272             for (var i = 0; i < pathParts.Length; i++)
273             {
274                 foreach (BindingExpressionPart part in GetPart(pathParts[i]))
275                 {
276                     last.NextPart = part;
277                     _parts.Add(part);
278                     last = part;
279                 }
280             }
281         }
282
283         void SetupPart(System.Reflection.TypeInfo sourceType, BindingExpressionPart part)
284         {
285             part.Arguments = null;
286             part.LastGetter = null;
287             part.LastSetter = null;
288
289             PropertyInfo property = null;
290             if (part.IsIndexer)
291             {
292                 if (sourceType.IsArray)
293                 {
294                     int index;
295                     if (!int.TryParse(part.Content, NumberStyles.Number, CultureInfo.InvariantCulture, out index))
296                         Console.WriteLine($"Binding : {part.Content} could not be parsed as an index for a {sourceType}");
297                     else
298                         part.Arguments = new object[] { index };
299
300                     part.LastGetter = sourceType.GetDeclaredMethod("Get");
301                     part.LastSetter = sourceType.GetDeclaredMethod("Set");
302                     part.SetterType = sourceType.GetElementType();
303                 }
304
305                 DefaultMemberAttribute defaultMember = sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true).OfType<DefaultMemberAttribute>().FirstOrDefault();
306                 string indexerName = defaultMember != null ? defaultMember.MemberName : "Item";
307
308                 part.IndexerName = indexerName;
309
310 #if NETSTANDARD2_0
311                 try
312                 {
313                     property = sourceType.GetDeclaredProperty(indexerName);
314                 }
315                 catch (AmbiguousMatchException)
316                 {
317                     // Get most derived instance of property
318                     foreach (var p in sourceType.GetProperties().Where(prop => prop.Name == indexerName))
319                     {
320                         if (property == null || property.DeclaringType.IsAssignableFrom(property.DeclaringType))
321                             property = p;
322                     }
323                 }
324 #else
325                 property = sourceType.GetDeclaredProperty(indexerName);
326 #endif
327
328                 if (property == null) //is the indexer defined on the base class?
329                     property = sourceType.BaseType?.GetProperty(indexerName);
330                 if (property == null) //is the indexer defined on implemented interface ?
331                 {
332                     foreach (var implementedInterface in sourceType.ImplementedInterfaces)
333                     {
334                         property = implementedInterface.GetProperty(indexerName);
335                         if (property != null)
336                             break;
337                     }
338                 }
339
340                 if (property != null)
341                 {
342                     ParameterInfo parameter = property.GetIndexParameters().FirstOrDefault();
343                     if (parameter != null)
344                     {
345                         try
346                         {
347                             object arg = Convert.ChangeType(part.Content, parameter.ParameterType, CultureInfo.InvariantCulture);
348                             part.Arguments = new[] { arg };
349                         }
350                         catch (FormatException)
351                         {
352                         }
353                         catch (InvalidCastException)
354                         {
355                         }
356                         catch (OverflowException)
357                         {
358                         }
359                     }
360                 }
361             }
362             else
363                 property = sourceType.GetDeclaredProperty(part.Content) ?? sourceType.BaseType?.GetProperty(part.Content);
364
365             if (property != null)
366             {
367                 if (property.CanRead && property.GetMethod != null)
368                 {
369                     if (property.GetMethod.IsPublic && !property.GetMethod.IsStatic)
370                     {
371                         part.LastGetter = property.GetMethod;
372                     }
373                 }
374                 if (property.CanWrite && property.SetMethod != null)
375                 {
376                     if (property.SetMethod.IsPublic && !property.SetMethod.IsStatic)
377                     {
378                         part.LastSetter = property.SetMethod;
379                         part.SetterType = part.LastSetter.GetParameters().Last().ParameterType;
380
381                         if (Binding.AllowChaining)
382                         {
383                             FieldInfo bindablePropertyField = sourceType.GetDeclaredField(part.Content + "Property");
384                             if (bindablePropertyField != null && bindablePropertyField.FieldType == typeof(BindableProperty) && sourceType.ImplementedInterfaces.Contains(typeof(IElementController)))
385                             {
386                                 MethodInfo setValueMethod = null;
387 #if NETSTANDARD1_0
388                                 foreach (MethodInfo m in sourceType.AsType().GetRuntimeMethods())
389                                 {
390                                     if (m.Name.EndsWith("IElementController.SetValueFromRenderer"))
391                                     {
392                                         ParameterInfo[] parameters = m.GetParameters();
393                                         if (parameters.Length == 2 && parameters[0].ParameterType == typeof(BindableProperty))
394                                         {
395                                             setValueMethod = m;
396                                             break;
397                                         }
398                                     }
399                                 }
400 #else
401                                 setValueMethod = typeof(IElementController).GetMethod("SetValueFromRenderer", new[] { typeof(BindableProperty), typeof(object) });
402 #endif
403                                 if (setValueMethod != null)
404                                 {
405                                     part.LastSetter = setValueMethod;
406                                     part.IsBindablePropertySetter = true;
407                                     part.BindablePropertyField = bindablePropertyField.GetValue(null);
408                                 }
409                             }
410                         }
411                     }
412                 }
413 #if !NETSTANDARD1_0
414                 //TupleElementNamesAttribute tupleEltNames;
415                 //if (property != null
416                 //    && part.NextPart != null
417                 //    && property.PropertyType.IsGenericType
418                 //    && (property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<>)
419                 //        || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,>)
420                 //        || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,>)
421                 //        || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,>)
422                 //        || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,>)
423                 //        || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,>)
424                 //        || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,>)
425                 //        || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,,>))
426                 //    && (tupleEltNames = property.GetCustomAttribute(typeof(TupleElementNamesAttribute)) as TupleElementNamesAttribute) != null)
427                 //{
428                 //    // modify the nextPart to access the tuple item via the ITuple indexer
429                 //    var nextPart = part.NextPart;
430                 //    var name = nextPart.Content;
431                 //    var index = tupleEltNames.TransformNames.IndexOf(name);
432                 //    if (index >= 0)
433                 //    {
434                 //        nextPart.IsIndexer = true;
435                 //        nextPart.Content = index.ToString();
436                 //    }
437                 //}
438 #endif
439             }
440
441         }
442         static Type[] DecimalTypes = new[] { typeof(float), typeof(decimal), typeof(double) };
443
444         bool TryConvert(ref object value, Type convertTo, bool toTarget)
445         {
446             if (value == null)
447                 return true;
448             if ((toTarget && _targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
449                 return true;
450
451             object original = value;
452             try
453             {
454                 var stringValue = value as string ?? string.Empty;
455                 // see: https://bugzilla.xamarin.com/show_bug.cgi?id=32871
456                 // do not canonicalize "*.[.]"; "1." should not update bound BindableProperty
457                 if (stringValue.EndsWith(".") && DecimalTypes.Contains(convertTo))
458                     throw new FormatException();
459
460                 // do not canonicalize "-0"; user will likely enter a period after "-0"
461                 if (stringValue == "-0" && DecimalTypes.Contains(convertTo))
462                     throw new FormatException();
463
464                 value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
465                 return true;
466             }
467             catch (InvalidCastException)
468             {
469                 value = original;
470                 return false;
471             }
472             catch (FormatException)
473             {
474                 value = original;
475                 return false;
476             }
477             catch (OverflowException)
478             {
479                 value = original;
480                 return false;
481             }
482         }
483
484         class BindingPair
485         {
486             public BindingPair(BindingExpressionPart part, object source, bool isLast)
487             {
488                 Part = part;
489                 Source = source;
490                 IsLast = isLast;
491             }
492
493             public bool IsLast { get; set; }
494
495             public BindingExpressionPart Part { get; private set; }
496
497             public object Source { get; private set; }
498         }
499
500         internal class WeakPropertyChangedProxy
501         {
502             readonly WeakReference<INotifyPropertyChanged> _source = new WeakReference<INotifyPropertyChanged>(null);
503             readonly WeakReference<PropertyChangedEventHandler> _listener = new WeakReference<PropertyChangedEventHandler>(null);
504             readonly PropertyChangedEventHandler _handler;
505             readonly EventHandler _bchandler;
506             internal WeakReference<INotifyPropertyChanged> Source => _source;
507
508             public WeakPropertyChangedProxy()
509             {
510                 _handler = new PropertyChangedEventHandler(OnPropertyChanged);
511                 _bchandler = new EventHandler(OnBCChanged);
512             }
513
514             public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this()
515             {
516                 SubscribeTo(source, listener);
517             }
518
519             public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
520             {
521                 source.PropertyChanged += _handler;
522                 var bo = source as BindableObject;
523                 if (bo != null)
524                     bo.BindingContextChanged += _bchandler;
525                 _source.SetTarget(source);
526                 _listener.SetTarget(listener);
527             }
528
529             public void Unsubscribe()
530             {
531                 INotifyPropertyChanged source;
532                 if (_source.TryGetTarget(out source) && source != null)
533                     source.PropertyChanged -= _handler;
534                 var bo = source as BindableObject;
535                 if (bo != null)
536                     bo.BindingContextChanged -= _bchandler;
537
538                 _source.SetTarget(null);
539                 _listener.SetTarget(null);
540             }
541
542             void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
543             {
544                 PropertyChangedEventHandler handler;
545                 if (_listener.TryGetTarget(out handler) && handler != null)
546                     handler(sender, e);
547                 else
548                     Unsubscribe();
549             }
550
551             void OnBCChanged(object sender, EventArgs e)
552             {
553                 OnPropertyChanged(sender, new PropertyChangedEventArgs("BindingContext"));
554             }
555         }
556
557         class BindingExpressionPart
558         {
559             readonly BindingExpression _expression;
560             readonly PropertyChangedEventHandler _changeHandler;
561             WeakPropertyChangedProxy _listener;
562
563             public BindingExpressionPart(BindingExpression expression, string content, bool isIndexer = false)
564             {
565                 _expression = expression;
566                 IsSelf = content == Tizen.NUI.Binding.Binding.SelfPath;
567                 Content = content;
568                 IsIndexer = isIndexer;
569
570                 _changeHandler = PropertyChanged;
571             }
572
573             public void Subscribe(INotifyPropertyChanged handler)
574             {
575                 INotifyPropertyChanged source;
576                 if (_listener != null && _listener.Source.TryGetTarget(out source) && ReferenceEquals(handler, source))
577                     // Already subscribed
578                     return;
579
580                 // Clear out the old subscription if necessary
581                 Unsubscribe();
582
583                 _listener = new WeakPropertyChangedProxy(handler, _changeHandler);
584             }
585
586             public void Unsubscribe()
587             {
588                 var listener = _listener;
589                 if (listener != null)
590                 {
591                     listener.Unsubscribe();
592                     _listener = null;
593                 }
594             }
595
596             public object[] Arguments { get; set; }
597
598             public object BindablePropertyField { get; set; }
599
600             public string Content { get; internal set; }
601
602             public string IndexerName { get; set; }
603
604             public bool IsBindablePropertySetter { get; set; }
605
606             public bool IsIndexer { get; internal set; }
607
608             public bool IsSelf { get; }
609
610             public MethodInfo LastGetter { get; set; }
611
612             public MethodInfo LastSetter { get; set; }
613
614             public BindingExpressionPart NextPart { get; set; }
615
616             public Type SetterType { get; set; }
617
618             public void PropertyChanged(object sender, PropertyChangedEventArgs args)
619             {
620                 BindingExpressionPart part = NextPart ?? this;
621
622                 string name = args.PropertyName;
623
624                 if (!string.IsNullOrEmpty(name))
625                 {
626                     if (part.IsIndexer)
627                     {
628                         if (name.Contains("["))
629                         {
630                             if (name != string.Format("{0}[{1}]", part.IndexerName, part.Content))
631                                 return;
632                         }
633                         else if (name != part.IndexerName)
634                             return;
635                     }
636                     else if (name != part.Content)
637                     {
638                         return;
639                     }
640                 }
641
642                 _expression.Apply();
643                 // Device.BeginInvokeOnMainThread(() => _expression.Apply());
644             }
645
646             public bool TryGetValue(object source, out object value)
647             {
648                 value = source;
649
650                 if (LastGetter != null && value != null)
651                 {
652                     if (IsIndexer)
653                     {
654                         try
655                         {
656                             value = LastGetter.Invoke(value, Arguments);
657                         }
658                         catch (TargetInvocationException ex)
659                         {
660                             if (!(ex.InnerException is KeyNotFoundException))
661                                 throw;
662                             value = null;
663                         }
664                         return true;
665                     }
666                     value = LastGetter.Invoke(value, Arguments);
667                     return true;
668                 }
669
670                 return false;
671             }
672         }
673     }
674 }