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);
}
}
}