2 * Copyright(c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.Globalization;
23 using System.Reflection;
24 using Tizen.NUI.Binding.Internals;
25 using System.Runtime.CompilerServices;
27 namespace Tizen.NUI.Binding
29 internal class BindingExpression
31 internal const string PropertyNotFoundErrorMessage = "'{0}' property not found on '{1}', target property: '{2}.{3}'";
33 readonly List<BindingExpressionPart> _parts = new List<BindingExpressionPart>();
35 BindableProperty _targetProperty;
36 WeakReference<object> _weakSource;
37 WeakReference<BindableObject> _weakTarget;
39 internal BindingExpression(BindingBase binding, string path)
42 throw new ArgumentNullException(nameof(binding));
44 throw new ArgumentNullException(nameof(path));
52 internal BindingBase Binding { get; }
54 internal string Path { get; }
57 /// Applies the binding expression to a previously set source and target.
59 internal void Apply(bool fromTarget = false)
61 if (_weakSource == null || _weakTarget == null)
64 BindableObject target;
65 if (!_weakTarget.TryGetTarget(out target))
72 if (_weakSource.TryGetTarget(out source) && _targetProperty != null)
73 ApplyCore(source, target, _targetProperty, fromTarget);
77 /// Applies the binding expression to a new source or target.
79 internal void Apply(object sourceObject, BindableObject target, BindableProperty property)
81 _targetProperty = property;
83 BindableObject prevTarget;
84 if (_weakTarget != null && _weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, target))
85 throw new InvalidOperationException("Binding instances can not be reused");
87 object previousSource;
88 if (_weakSource != null && _weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, sourceObject))
89 throw new InvalidOperationException("Binding instances can not be reused");
91 _weakSource = new WeakReference<object>(sourceObject);
92 _weakTarget = new WeakReference<BindableObject>(target);
94 ApplyCore(sourceObject, target, property);
97 internal void Unapply()
100 if (_weakSource != null && _weakSource.TryGetTarget(out sourceObject))
102 for (var i = 0; i < _parts.Count; i++)
104 if (i + 1 == _parts.Count) //do not handle the last.
107 BindingExpressionPart part = _parts[i];
111 part.TryGetValue(sourceObject, out sourceObject);
123 /// Applies the binding expression to a previously set source or target.
125 void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false)
127 BindingMode mode = Binding.GetRealizedMode(_targetProperty);
128 if ((mode == BindingMode.OneWay || mode == BindingMode.OneTime) && fromTarget)
131 bool needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay || mode == BindingMode.OneTime;
132 bool needsSetter = !needsGetter && ((mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource);
134 object current = sourceObject;
135 object previous = null;
136 BindingExpressionPart part = null;
138 for (var i = 0; i < _parts.Count; i++)
141 bool isLast = i + 1 == _parts.Count;
143 if (!part.IsSelf && current != null)
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);
152 part.TryGetValue(current, out current);
155 if (!part.IsSelf && current != null)
157 if ((needsGetter && part.LastGetter == null) || (needsSetter && part.NextPart == null && part.LastSetter == null))
159 Console.WriteLine("Binding, " + PropertyNotFoundErrorMessage, part.Content, current, target.GetType(), property.PropertyName);
164 if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay)
166 var inpc = current as INotifyPropertyChanged;
167 if (inpc != null && !ReferenceEquals(current, previous))
168 part.Subscribe(inpc);
174 Debug.Assert(part != null, "There should always be at least the self part in the expression.");
178 object value = property.DefaultValue;
179 if (part.TryGetValue(current, out value) || part.IsSelf)
181 value = Binding.GetSourceValue(value, property.ReturnType);
184 value = property.DefaultValue;
186 if (!TryConvert(ref value, property.ReturnType, true))
188 Console.WriteLine($"Binding : {value} can not be converted to type {property.ReturnType}");
192 target.SetValueCore(property, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted);
194 else if (needsSetter && part.LastSetter != null && current != null)
196 object value = Binding.GetTargetValue(target.GetValue(property), part.SetterType);
198 if (!TryConvert(ref value, part.SetterType, false))
200 Console.WriteLine($"Binding : {value} can not be converted to type {part.SetterType}");
207 args = new object[part.Arguments.Length + 1];
208 part.Arguments.CopyTo(args, 0);
209 args[args.Length - 1] = value;
211 else if (part.IsBindablePropertySetter)
213 args = new[] { part.BindablePropertyField, value };
217 args = new[] { value };
220 part.LastSetter.Invoke(current, args);
224 IEnumerable<BindingExpressionPart> GetPart(string part)
227 if (string.IsNullOrEmpty(part))
228 throw new FormatException("Path contains an empty part");
230 BindingExpressionPart indexer = null;
232 int lbIndex = part.IndexOf('[');
235 int rbIndex = part.LastIndexOf(']');
237 throw new FormatException("Indexer did not contain closing bracket");
239 int argLength = rbIndex - lbIndex - 1;
241 throw new FormatException("Indexer did not contain arguments");
243 string argString = part.Substring(lbIndex + 1, argLength);
244 indexer = new BindingExpressionPart(this, argString, true);
246 part = part.Substring(0, lbIndex);
251 yield return new BindingExpressionPart(this, part);
253 yield return indexer;
258 string p = Path.Trim();
260 var last = new BindingExpressionPart(this, ".");
271 string[] pathParts = p.Split('.');
272 for (var i = 0; i < pathParts.Length; i++)
274 foreach (BindingExpressionPart part in GetPart(pathParts[i]))
276 last.NextPart = part;
283 void SetupPart(System.Reflection.TypeInfo sourceType, BindingExpressionPart part)
285 part.Arguments = null;
286 part.LastGetter = null;
287 part.LastSetter = null;
289 PropertyInfo property = null;
292 if (sourceType.IsArray)
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}");
298 part.Arguments = new object[] { index };
300 part.LastGetter = sourceType.GetDeclaredMethod("Get");
301 part.LastSetter = sourceType.GetDeclaredMethod("Set");
302 part.SetterType = sourceType.GetElementType();
305 DefaultMemberAttribute defaultMember = sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true).OfType<DefaultMemberAttribute>().FirstOrDefault();
306 string indexerName = defaultMember != null ? defaultMember.MemberName : "Item";
308 part.IndexerName = indexerName;
313 property = sourceType.GetDeclaredProperty(indexerName);
315 catch (AmbiguousMatchException)
317 // Get most derived instance of property
318 foreach (var p in sourceType.GetProperties().Where(prop => prop.Name == indexerName))
320 if (property == null || property.DeclaringType.IsAssignableFrom(property.DeclaringType))
325 property = sourceType.GetDeclaredProperty(indexerName);
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 ?
332 foreach (var implementedInterface in sourceType.ImplementedInterfaces)
334 property = implementedInterface.GetProperty(indexerName);
335 if (property != null)
340 if (property != null)
342 ParameterInfo parameter = property.GetIndexParameters().FirstOrDefault();
343 if (parameter != null)
347 object arg = Convert.ChangeType(part.Content, parameter.ParameterType, CultureInfo.InvariantCulture);
348 part.Arguments = new[] { arg };
350 catch (FormatException)
353 catch (InvalidCastException)
356 catch (OverflowException)
363 property = sourceType.GetDeclaredProperty(part.Content) ?? sourceType.BaseType?.GetProperty(part.Content);
365 if (property != null)
367 if (property.CanRead && property.GetMethod != null)
369 if (property.GetMethod.IsPublic && !property.GetMethod.IsStatic)
371 part.LastGetter = property.GetMethod;
374 if (property.CanWrite && property.SetMethod != null)
376 if (property.SetMethod.IsPublic && !property.SetMethod.IsStatic)
378 part.LastSetter = property.SetMethod;
379 part.SetterType = part.LastSetter.GetParameters().Last().ParameterType;
381 if (Binding.AllowChaining)
383 FieldInfo bindablePropertyField = sourceType.GetDeclaredField(part.Content + "Property");
384 if (bindablePropertyField != null && bindablePropertyField.FieldType == typeof(BindableProperty) && sourceType.ImplementedInterfaces.Contains(typeof(IElementController)))
386 MethodInfo setValueMethod = null;
388 foreach (MethodInfo m in sourceType.AsType().GetRuntimeMethods())
390 if (m.Name.EndsWith("IElementController.SetValueFromRenderer"))
392 ParameterInfo[] parameters = m.GetParameters();
393 if (parameters.Length == 2 && parameters[0].ParameterType == typeof(BindableProperty))
401 setValueMethod = typeof(IElementController).GetMethod("SetValueFromRenderer", new[] { typeof(BindableProperty), typeof(object) });
403 if (setValueMethod != null)
405 part.LastSetter = setValueMethod;
406 part.IsBindablePropertySetter = true;
407 part.BindablePropertyField = bindablePropertyField.GetValue(null);
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)
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);
434 // nextPart.IsIndexer = true;
435 // nextPart.Content = index.ToString();
442 static Type[] DecimalTypes = new[] { typeof(float), typeof(decimal), typeof(double) };
444 bool TryConvert(ref object value, Type convertTo, bool toTarget)
448 if ((toTarget && _targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
451 object original = value;
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();
460 // do not canonicalize "-0"; user will likely enter a period after "-0"
461 if (stringValue == "-0" && DecimalTypes.Contains(convertTo))
462 throw new FormatException();
464 value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
467 catch (InvalidCastException)
472 catch (FormatException)
477 catch (OverflowException)
486 public BindingPair(BindingExpressionPart part, object source, bool isLast)
493 public bool IsLast { get; set; }
495 public BindingExpressionPart Part { get; private set; }
497 public object Source { get; private set; }
500 internal class WeakPropertyChangedProxy
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;
508 public WeakPropertyChangedProxy()
510 _handler = new PropertyChangedEventHandler(OnPropertyChanged);
511 _bchandler = new EventHandler(OnBCChanged);
514 public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this()
516 SubscribeTo(source, listener);
519 public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
521 source.PropertyChanged += _handler;
522 var bo = source as BindableObject;
524 bo.BindingContextChanged += _bchandler;
525 _source.SetTarget(source);
526 _listener.SetTarget(listener);
529 public void Unsubscribe()
531 INotifyPropertyChanged source;
532 if (_source.TryGetTarget(out source) && source != null)
533 source.PropertyChanged -= _handler;
534 var bo = source as BindableObject;
536 bo.BindingContextChanged -= _bchandler;
538 _source.SetTarget(null);
539 _listener.SetTarget(null);
542 void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
544 PropertyChangedEventHandler handler;
545 if (_listener.TryGetTarget(out handler) && handler != null)
551 void OnBCChanged(object sender, EventArgs e)
553 OnPropertyChanged(sender, new PropertyChangedEventArgs("BindingContext"));
557 class BindingExpressionPart
559 readonly BindingExpression _expression;
560 readonly PropertyChangedEventHandler _changeHandler;
561 WeakPropertyChangedProxy _listener;
563 public BindingExpressionPart(BindingExpression expression, string content, bool isIndexer = false)
565 _expression = expression;
566 IsSelf = content == Tizen.NUI.Binding.Binding.SelfPath;
568 IsIndexer = isIndexer;
570 _changeHandler = PropertyChanged;
573 public void Subscribe(INotifyPropertyChanged handler)
575 INotifyPropertyChanged source;
576 if (_listener != null && _listener.Source.TryGetTarget(out source) && ReferenceEquals(handler, source))
577 // Already subscribed
580 // Clear out the old subscription if necessary
583 _listener = new WeakPropertyChangedProxy(handler, _changeHandler);
586 public void Unsubscribe()
588 var listener = _listener;
589 if (listener != null)
591 listener.Unsubscribe();
596 public object[] Arguments { get; set; }
598 public object BindablePropertyField { get; set; }
600 public string Content { get; internal set; }
602 public string IndexerName { get; set; }
604 public bool IsBindablePropertySetter { get; set; }
606 public bool IsIndexer { get; internal set; }
608 public bool IsSelf { get; }
610 public MethodInfo LastGetter { get; set; }
612 public MethodInfo LastSetter { get; set; }
614 public BindingExpressionPart NextPart { get; set; }
616 public Type SetterType { get; set; }
618 public void PropertyChanged(object sender, PropertyChangedEventArgs args)
620 BindingExpressionPart part = NextPart ?? this;
622 string name = args.PropertyName;
624 if (!string.IsNullOrEmpty(name))
628 if (name.Contains("["))
630 if (name != string.Format("{0}[{1}]", part.IndexerName, part.Content))
633 else if (name != part.IndexerName)
636 else if (name != part.Content)
643 // Device.BeginInvokeOnMainThread(() => _expression.Apply());
646 public bool TryGetValue(object source, out object value)
650 if (LastGetter != null && value != null)
656 value = LastGetter.Invoke(value, Arguments);
658 catch (TargetInvocationException ex)
660 if (!(ex.InnerException is KeyNotFoundException))
666 value = LastGetter.Invoke(value, Arguments);