using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using Avalonia.Data.Core.Parsers; using Avalonia.Data.Core.Plugins; using Avalonia.Reactive; namespace Avalonia.Data.Core { /// /// Observes and sets the value of an expression on an object. /// public class ExpressionObserver : LightweightObservableBase, IDescription { /// /// An ordered collection of property accessor plugins that can be used to customize /// the reading and subscription of property values on a type. /// public static readonly List PropertyAccessors = new List { new AvaloniaPropertyAccessorPlugin(), new MethodAccessorPlugin(), new InpcPropertyAccessorPlugin(), }; /// /// An ordered collection of validation checker plugins that can be used to customize /// the validation of view model and model data. /// public static readonly List DataValidators = new List { new DataAnnotationsValidationPlugin(), new IndeiValidationPlugin(), new ExceptionValidationPlugin(), }; /// /// An ordered collection of stream plugins that can be used to customize the behavior /// of the '^' stream binding operator. /// public static readonly List StreamHandlers = new List { new TaskStreamPlugin(), new ObservableStreamPlugin(), }; private readonly ExpressionNode _node; private object? _root; private Func? _rootGetter; private IDisposable? _rootSubscription; private WeakReference? _value; private IReadOnlyList? _transformNodes; /// /// Initializes a new instance of the class. /// /// The root object. /// The expression. /// /// A description of the expression. /// public ExpressionObserver( object? root, ExpressionNode node, string? description = null) { _node = node; Description = description; _root = new WeakReference(root == AvaloniaProperty.UnsetValue ? null : root); } /// /// Initializes a new instance of the class. /// /// An observable which provides the root object. /// The expression. /// /// A description of the expression. /// public ExpressionObserver( IObservable rootObservable, ExpressionNode node, string? description) { _ = rootObservable ??throw new ArgumentNullException(nameof(rootObservable)); _node = node; Description = description; _root = rootObservable; } /// /// Initializes a new instance of the class. /// /// A function which gets the root object. /// The expression. /// An observable which triggers a re-read of the getter. Generic argument value is not used. /// /// A description of the expression. /// public ExpressionObserver( Func rootGetter, ExpressionNode node, IObservable update, string? description) { Description = description; _rootGetter = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter)); _node = node ?? throw new ArgumentNullException(nameof(node)); _root = update.Select(x => rootGetter()); } /// /// Creates a new instance of the class. /// /// The root object. /// The expression. /// Whether or not to track data validation /// /// A description of the expression. If null, 's string representation will be used. /// [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)] public static ExpressionObserver Create( T? root, Expression> expression, bool enableDataValidation = false, string? description = null) { return new ExpressionObserver(root, Parse(expression, enableDataValidation), description ?? expression.ToString()); } /// /// Creates a new instance of the class. /// /// An observable which provides the root object. /// The expression. /// Whether or not to track data validation /// /// A description of the expression. If null, 's string representation will be used. /// [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)] public static ExpressionObserver Create( IObservable rootObservable, Expression> expression, bool enableDataValidation = false, string? description = null) { _ = rootObservable ?? throw new ArgumentNullException(nameof(rootObservable)); return new ExpressionObserver( rootObservable.Select(o => (object?)o), Parse(expression, enableDataValidation), description ?? expression.ToString()); } /// /// Creates a new instance of the class. /// /// A function which gets the root object. /// The expression. /// An observable which triggers a re-read of the getter. Generic argument value is not used. /// Whether or not to track data validation /// /// A description of the expression. If null, 's string representation will be used. /// [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)] public static ExpressionObserver Create( Func rootGetter, Expression> expression, IObservable update, bool enableDataValidation = false, string? description = null) { _ = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter)); return new ExpressionObserver( () => rootGetter(), Parse(expression, enableDataValidation), update, description ?? expression.ToString()); } private IReadOnlyList GetTransformNodesFromChain() { LinkedList transforms = new LinkedList(); var node = _node; while (node != null) { if (node is ITransformNode transform) { transforms.AddFirst(transform); } node = node.Next; } return new List(transforms); } private IReadOnlyList TransformNodes => (_transformNodes ?? (_transformNodes = GetTransformNodesFromChain())); /// /// Attempts to set the value of a property expression. /// /// The value to set. /// The binding priority to use. /// /// True if the value could be set; false if the expression does not evaluate to a /// property. Note that the must be subscribed to /// before setting the target value can work, as setting the value requires the /// expression to be evaluated. /// public bool SetValue(object? value, BindingPriority priority = BindingPriority.LocalValue) { if (Leaf is SettableNode settable) { foreach (var transform in TransformNodes) { value = transform.Transform(value); if (value is BindingNotification) { return false; } } return settable.SetTargetValue(value, priority); } return false; } /// /// Gets a description of the expression being observed. /// public string? Description { get; } /// /// Gets the expression being observed. /// public string? Expression { get; } /// /// Gets the type of the expression result or null if the expression could not be /// evaluated. /// public Type? ResultType => (Leaf as SettableNode)?.PropertyType; /// /// Gets the leaf node. /// private ExpressionNode Leaf { get { var node = _node; while (node.Next != null) node = node.Next; return node; } } protected override void Initialize() { _value = null; if (_rootGetter is not null) _node.Target = new WeakReference(_rootGetter()); _node.Subscribe(ValueChanged); StartRoot(); } protected override void Deinitialize() { _rootSubscription?.Dispose(); _rootSubscription = null; _node.Unsubscribe(); } protected override void Subscribed(IObserver observer, bool first) { if (!first && _value != null && _value.TryGetTarget(out var value)) { observer.OnNext(value); } } [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)] private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation) { return ExpressionTreeParser.Parse(expression, enableDataValidation); } private void StartRoot() { if (_root is IObservable observable) { _rootSubscription = observable.Subscribe( new AnonymousObserver( x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), x => PublishCompleted(), PublishCompleted)); } else { _node.Target = (WeakReference)_root!; } } private void ValueChanged(object? value) { var broken = BindingNotification.ExtractError(value) as MarkupBindingChainException; broken?.Commit(Description ?? "{empty}"); _value = new WeakReference(value); PublishNext(value); } } }