// Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using Perspex.Data; using Perspex.Markup.Data.Plugins; namespace Perspex.Markup.Data { /// /// Observes and sets the value of an expression on an object. /// public class ExpressionObserver : ObservableBase, 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 IList PropertyAccessors = new List { new PerspexPropertyAccessorPlugin(), 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 IList ValidationCheckers = new List { new IndeiValidationCheckerPlugin(), new ExceptionValidationCheckerPlugin() }; private readonly WeakReference _root; private readonly Func _rootGetter; private readonly IObservable _rootObservable; private readonly IObservable _update; private IDisposable _rootObserverSubscription; private IDisposable _updateSubscription; private int _count; private readonly ExpressionNode _node; private ValidationMethods _methods; /// /// Initializes a new instance of the class. /// /// The root object. /// The expression. /// The validation methods to enable on this observer. public ExpressionObserver(object root, string expression, ValidationMethods methods = ValidationMethods.None) { Contract.Requires(expression != null); _root = new WeakReference(root); _methods = methods; if (!string.IsNullOrWhiteSpace(expression)) { _node = ExpressionNodeBuilder.Build(expression); } Expression = expression; } /// /// Initializes a new instance of the class. /// /// An observable which provides the root object. /// The expression. /// The validation methods to enable on this observer. public ExpressionObserver(IObservable rootObservable, string expression, ValidationMethods methods = ValidationMethods.None) { Contract.Requires(rootObservable != null); Contract.Requires(expression != null); _rootObservable = rootObservable; _methods = methods; if (!string.IsNullOrWhiteSpace(expression)) { _node = ExpressionNodeBuilder.Build(expression); } Expression = expression; } /// /// 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. /// The validation methods to enable on this observer. public ExpressionObserver( Func rootGetter, string expression, IObservable update, ValidationMethods methods = ValidationMethods.None) { Contract.Requires(rootGetter != null); Contract.Requires(expression != null); Contract.Requires(update != null); _rootGetter = rootGetter; _update = update; _methods = methods; if (!string.IsNullOrWhiteSpace(expression)) { _node = ExpressionNodeBuilder.Build(expression); } Expression = expression; } /// /// 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. /// public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue) { IncrementCount(); if (_rootGetter != null && _node != null) { _node.Target = new WeakReference(_rootGetter()); } try { return _node?.SetValue(value, priority) ?? false; } finally { DecrementCount(); } } /// /// 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 { get { IncrementCount(); try { if (_node != null) { return (Leaf as PropertyAccessorNode)?.PropertyType; } else if(_rootGetter != null) { return _rootGetter()?.GetType(); } else { return _root.Target?.GetType(); } } finally { DecrementCount(); } } } /// string IDescription.Description => Expression; /// /// Gets the root expression node. Used for testing. /// internal ExpressionNode Node => _node; /// /// Gets the leaf node. /// private ExpressionNode Leaf { get { var node = _node; while (node.Next != null) node = node.Next; return node; } } /// protected override IDisposable SubscribeCore(IObserver observer) { IncrementCount(); if (_node != null) { IObservable source = _node; if (_rootObservable != null) { source = source.TakeUntil(_rootObservable.LastOrDefaultAsync()); } else if (_update != null) { source = source.TakeUntil(_update.LastOrDefaultAsync()); } var validationFiltered = source.Where(o => (o as ValidationStatus)?.Match(_methods) ?? true); var subscription = validationFiltered.Subscribe(observer); return Disposable.Create(() => { DecrementCount(); subscription.Dispose(); }); } else if (_rootObservable != null) { return _rootObservable.Subscribe(observer); } else { if (_update == null) { return Observable.Never() .StartWith(_root.Target) .Subscribe(observer); } else { return _update .Select(_ => _rootGetter()) .StartWith(_rootGetter()) .Subscribe(observer); } } } private void IncrementCount() { if (_count++ == 0 && _node != null) { if (_rootGetter != null) { _node.Target = new WeakReference(_rootGetter()); if (_update != null) { _updateSubscription = _update.Subscribe(x => _node.Target = new WeakReference(_rootGetter())); } } else if (_rootObservable != null) { _rootObserverSubscription = _rootObservable.Subscribe(x => _node.Target = new WeakReference(x)); } else { _node.Target = _root; } } } private void DecrementCount() { if (--_count == 0 && _node != null) { if (_rootObserverSubscription != null) { _rootObserverSubscription.Dispose(); _rootObserverSubscription = null; } if (_updateSubscription != null) { _updateSubscription.Dispose(); _updateSubscription = null; } _node.Target = null; } } } }