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