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 - 1; i++)
104 BindingExpressionPart part = _parts[i];
108 part.TryGetValue(sourceObject, out sourceObject);
120 /// Applies the binding expression to a previously set source or target.
122 void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false)
124 BindingMode mode = Binding.GetRealizedMode(_targetProperty);
125 if ((mode == BindingMode.OneWay || mode == BindingMode.OneTime) && fromTarget)
128 bool needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay || mode == BindingMode.OneTime;
129 bool needsSetter = !needsGetter && ((mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource);
131 object current = sourceObject;
132 object previous = null;
133 BindingExpressionPart part = null;
135 for (var i = 0; i < _parts.Count; i++)
138 bool isLast = i + 1 == _parts.Count;
140 if (!part.IsSelf && current != null)
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);
149 part.TryGetValue(current, out current);
152 if (!part.IsSelf && current != null)
154 if ((needsGetter && part.LastGetter == null) || (needsSetter && part.NextPart == null && part.LastSetter == null))
156 Console.WriteLine("Binding, " + PropertyNotFoundErrorMessage, part.Content, current, target.GetType(), property.PropertyName);
161 if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay)
163 var inpc = current as INotifyPropertyChanged;
164 if (inpc != null && !ReferenceEquals(current, previous))
165 part.Subscribe(inpc);
171 Debug.Assert(part != null, "There should always be at least the self part in the expression.");
175 object value = property.DefaultValue;
176 if (part.TryGetValue(current, out value) || part.IsSelf)
178 value = Binding.GetSourceValue(value, property.ReturnType);
181 value = property.DefaultValue;
183 if (!TryConvert(ref value, property.ReturnType, true))
185 Console.WriteLine($"Binding : {value} can not be converted to type {property.ReturnType}");
189 target.SetValueCore(property, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted, false);
191 else if (needsSetter && part.LastSetter != null && current != null)
193 object value = Binding.GetTargetValue(target.GetValue(property), part.SetterType);
195 if (!TryConvert(ref value, part.SetterType, false))
197 Console.WriteLine($"Binding : {value} can not be converted to type {part.SetterType}");
204 args = new object[part.Arguments.Length + 1];
205 part.Arguments.CopyTo(args, 0);
206 args[args.Length - 1] = value;
208 else if (part.IsBindablePropertySetter)
210 args = new[] { part.BindablePropertyField, value };
214 args = new[] { value };
217 part.LastSetter.Invoke(current, args);
221 IEnumerable<BindingExpressionPart> GetPart(string part)
224 if (string.IsNullOrEmpty(part))
225 throw new FormatException("Path contains an empty part");
227 BindingExpressionPart indexer = null;
229 int lbIndex = part.IndexOf('[');
232 int rbIndex = part.LastIndexOf(']');
234 throw new FormatException("Indexer did not contain closing bracket");
236 int argLength = rbIndex - lbIndex - 1;
238 throw new FormatException("Indexer did not contain arguments");
240 string argString = part.Substring(lbIndex + 1, argLength);
241 indexer = new BindingExpressionPart(this, argString, true);
243 part = part.Substring(0, lbIndex);
248 yield return new BindingExpressionPart(this, part);
250 yield return indexer;
255 string p = Path.Trim();
257 var last = new BindingExpressionPart(this, ".");
268 string[] pathParts = p.Split('.');
269 for (var i = 0; i < pathParts.Length; i++)
271 foreach (BindingExpressionPart part in GetPart(pathParts[i]))
273 last.NextPart = part;
280 void SetupPart(System.Reflection.TypeInfo sourceType, BindingExpressionPart part)
282 part.Arguments = null;
283 part.LastGetter = null;
284 part.LastSetter = null;
286 PropertyInfo property = null;
289 if (sourceType.IsArray)
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}");
295 part.Arguments = new object[] { index };
297 part.LastGetter = sourceType.GetDeclaredMethod("Get");
298 part.LastSetter = sourceType.GetDeclaredMethod("Set");
299 part.SetterType = sourceType.GetElementType();
302 DefaultMemberAttribute defaultMember = sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true).OfType<DefaultMemberAttribute>().FirstOrDefault();
303 string indexerName = defaultMember != null ? defaultMember.MemberName : "Item";
305 part.IndexerName = indexerName;
310 property = sourceType.GetDeclaredProperty(indexerName);
312 catch (AmbiguousMatchException)
314 // Get most derived instance of property
315 foreach (var p in sourceType.GetProperties().Where(prop => prop.Name == indexerName))
317 if (property == null || property.DeclaringType.IsAssignableFrom(property.DeclaringType))
322 property = sourceType.GetDeclaredProperty(indexerName);
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 ?
329 foreach (var implementedInterface in sourceType.ImplementedInterfaces)
331 property = implementedInterface.GetProperty(indexerName);
332 if (property != null)
337 if (property != null)
339 ParameterInfo parameter = property.GetIndexParameters().FirstOrDefault();
340 if (parameter != null)
344 object arg = Convert.ChangeType(part.Content, parameter.ParameterType, CultureInfo.InvariantCulture);
345 part.Arguments = new[] { arg };
347 catch (FormatException)
350 catch (InvalidCastException)
353 catch (OverflowException)
360 property = sourceType.GetDeclaredProperty(part.Content) ?? sourceType.BaseType?.GetProperty(part.Content);
362 if (property != null)
364 if (property.CanRead && property.GetMethod != null)
366 if (property.GetMethod.IsPublic && !property.GetMethod.IsStatic)
368 part.LastGetter = property.GetMethod;
371 if (property.CanWrite && property.SetMethod != null)
373 if (property.SetMethod.IsPublic && !property.SetMethod.IsStatic)
375 part.LastSetter = property.SetMethod;
376 part.SetterType = part.LastSetter.GetParameters().Last().ParameterType;
378 if (Binding.AllowChaining)
380 FieldInfo bindablePropertyField = sourceType.GetDeclaredField(part.Content + "Property");
381 if (bindablePropertyField != null && bindablePropertyField.FieldType == typeof(BindableProperty) && sourceType.ImplementedInterfaces.Contains(typeof(IElementController)))
383 MethodInfo setValueMethod = null;
385 foreach (MethodInfo m in sourceType.AsType().GetRuntimeMethods())
387 if (m.Name.EndsWith("IElementController.SetValueFromRenderer"))
389 ParameterInfo[] parameters = m.GetParameters();
390 if (parameters.Length == 2 && parameters[0].ParameterType == typeof(BindableProperty))
398 setValueMethod = typeof(IElementController).GetMethod("SetValueFromRenderer", new[] { typeof(BindableProperty), typeof(object) });
400 if (setValueMethod != null)
402 part.LastSetter = setValueMethod;
403 part.IsBindablePropertySetter = true;
404 part.BindablePropertyField = bindablePropertyField.GetValue(null);
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)
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);
431 // nextPart.IsIndexer = true;
432 // nextPart.Content = index.ToString();
439 static Type[] DecimalTypes = new[] { typeof(float), typeof(decimal), typeof(double) };
441 bool TryConvert(ref object value, Type convertTo, bool toTarget)
445 if ((toTarget && _targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
448 object original = value;
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();
457 // do not canonicalize "-0"; user will likely enter a period after "-0"
458 if (stringValue == "-0" && DecimalTypes.Contains(convertTo))
459 throw new FormatException();
461 value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
464 catch (InvalidCastException)
469 catch (FormatException)
474 catch (OverflowException)
483 public BindingPair(BindingExpressionPart part, object source, bool isLast)
490 public bool IsLast { get; set; }
492 public BindingExpressionPart Part { get; private set; }
494 public object Source { get; private set; }
497 internal class WeakPropertyChangedProxy
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;
505 public WeakPropertyChangedProxy()
507 _handler = new PropertyChangedEventHandler(OnPropertyChanged);
508 _bchandler = new EventHandler(OnBCChanged);
511 public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this()
513 SubscribeTo(source, listener);
516 public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
518 source.PropertyChanged += _handler;
519 var bo = source as BindableObject;
521 bo.BindingContextChanged += _bchandler;
522 _source.SetTarget(source);
523 _listener.SetTarget(listener);
526 public void Unsubscribe()
528 INotifyPropertyChanged source;
529 if (_source.TryGetTarget(out source) && source != null)
530 source.PropertyChanged -= _handler;
531 var bo = source as BindableObject;
533 bo.BindingContextChanged -= _bchandler;
535 _source.SetTarget(null);
536 _listener.SetTarget(null);
539 void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
541 PropertyChangedEventHandler handler;
542 if (_listener.TryGetTarget(out handler) && handler != null)
548 void OnBCChanged(object sender, EventArgs e)
550 OnPropertyChanged(sender, new PropertyChangedEventArgs("BindingContext"));
554 class BindingExpressionPart
556 readonly BindingExpression _expression;
557 readonly PropertyChangedEventHandler _changeHandler;
558 WeakPropertyChangedProxy _listener;
560 public BindingExpressionPart(BindingExpression expression, string content, bool isIndexer = false)
562 _expression = expression;
563 IsSelf = content == Tizen.NUI.Binding.Binding.SelfPath;
565 IsIndexer = isIndexer;
567 _changeHandler = PropertyChanged;
570 public void Subscribe(INotifyPropertyChanged handler)
572 INotifyPropertyChanged source;
573 if (_listener != null && _listener.Source.TryGetTarget(out source) && ReferenceEquals(handler, source))
574 // Already subscribed
577 // Clear out the old subscription if necessary
580 _listener = new WeakPropertyChangedProxy(handler, _changeHandler);
583 public void Unsubscribe()
585 var listener = _listener;
586 if (listener != null)
588 listener.Unsubscribe();
593 public object[] Arguments { get; set; }
595 public object BindablePropertyField { get; set; }
597 public string Content { get; internal set; }
599 public string IndexerName { get; set; }
601 public bool IsBindablePropertySetter { get; set; }
603 public bool IsIndexer { get; internal set; }
605 public bool IsSelf { get; }
607 public MethodInfo LastGetter { get; set; }
609 public MethodInfo LastSetter { get; set; }
611 public BindingExpressionPart NextPart { get; set; }
613 public Type SetterType { get; set; }
615 public void PropertyChanged(object sender, PropertyChangedEventArgs args)
617 BindingExpressionPart part = NextPart ?? this;
619 string name = args.PropertyName;
621 if (!string.IsNullOrEmpty(name))
625 if (name.Contains("["))
627 if (name != string.Format("{0}[{1}]", part.IndexerName, part.Content))
630 else if (name != part.IndexerName)
633 else if (name != part.Content)
640 // Device.BeginInvokeOnMainThread(() => _expression.Apply());
643 public bool TryGetValue(object source, out object value)
647 if (LastGetter != null && value != null)
653 value = LastGetter.Invoke(value, Arguments);
655 catch (TargetInvocationException ex)
657 if (!(ex.InnerException is KeyNotFoundException))
663 value = LastGetter.Invoke(value, Arguments);