using System; namespace Avalonia.Data.Core { public abstract class ExpressionNode { private static readonly object CacheInvalid = new object(); protected static readonly WeakReference UnsetReference = new WeakReference(AvaloniaProperty.UnsetValue); protected static readonly WeakReference NullReference = new WeakReference(null); private WeakReference _target = UnsetReference; private Action? _subscriber; private bool _listening; protected WeakReference? LastValue { get; private set; } public abstract string? Description { get; } public ExpressionNode? Next { get; set; } public WeakReference Target { get { return _target; } set { _ = value ?? throw new ArgumentNullException(nameof(value)); _target.TryGetTarget(out var oldTarget); value.TryGetTarget(out var newTarget); if (!ReferenceEquals(oldTarget, newTarget)) { if (_listening) { StopListening(); } _target = value; if (_subscriber != null) { StartListening(); } } } } public void Subscribe(Action subscriber) { if (_subscriber != null) { throw new AvaloniaInternalException("ExpressionNode can only be subscribed once."); } _subscriber = subscriber; Next?.Subscribe(NextValueChanged); StartListening(); } public void Unsubscribe() { Next?.Unsubscribe(); if (_listening) { StopListening(); } LastValue = null; _subscriber = null; } protected virtual void StartListeningCore(WeakReference reference) { reference.TryGetTarget(out var target); ValueChanged(target); } protected virtual void StopListeningCore() { } protected virtual void NextValueChanged(object? value) { if (_subscriber is null) return; var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingChainException; bindingBroken?.AddNode(Description ?? "{empty}"); _subscriber(value); } protected void ValueChanged(object? value) => ValueChanged(value, true); private void ValueChanged(object? value, bool notify) { if (_subscriber is { } subscriber) { var notification = value as BindingNotification; var next = Next; if (notification == null) { LastValue = value != null ? new WeakReference(value) : NullReference; if (next != null) { next.Target = LastValue; } else if (notify) { subscriber(value); } } else { LastValue = notification.Value != null ? new WeakReference(notification.Value) : NullReference; if (next != null) { next.Target = LastValue; } if (next == null || notification.Error != null) { subscriber(value); } } } } private void StartListening() { _target.TryGetTarget(out var target); if (target == null) { ValueChanged(TargetNullNotification()); _listening = false; } else if (target != AvaloniaProperty.UnsetValue) { _listening = true; StartListeningCore(_target!); } else { ValueChanged(AvaloniaProperty.UnsetValue, notify:false); _listening = false; } } private void StopListening() { StopListeningCore(); _listening = false; } private BindingNotification TargetNullNotification() { return new BindingNotification( new MarkupBindingChainException("Null value"), BindingErrorType.Error, AvaloniaProperty.UnsetValue); } } }