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