ExpressionObserver.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq.Expressions;
  6. using System.Reactive;
  7. using System.Reactive.Linq;
  8. using Avalonia.Data.Core.Parsers;
  9. using Avalonia.Data.Core.Plugins;
  10. using Avalonia.Reactive;
  11. namespace Avalonia.Data.Core
  12. {
  13. /// <summary>
  14. /// Observes and sets the value of an expression on an object.
  15. /// </summary>
  16. public class ExpressionObserver : LightweightObservableBase<object>, IDescription
  17. {
  18. /// <summary>
  19. /// An ordered collection of property accessor plugins that can be used to customize
  20. /// the reading and subscription of property values on a type.
  21. /// </summary>
  22. public static readonly IList<IPropertyAccessorPlugin> PropertyAccessors =
  23. new List<IPropertyAccessorPlugin>
  24. {
  25. new AvaloniaPropertyAccessorPlugin(),
  26. new MethodAccessorPlugin(),
  27. new InpcPropertyAccessorPlugin(),
  28. };
  29. /// <summary>
  30. /// An ordered collection of validation checker plugins that can be used to customize
  31. /// the validation of view model and model data.
  32. /// </summary>
  33. public static readonly IList<IDataValidationPlugin> DataValidators =
  34. new List<IDataValidationPlugin>
  35. {
  36. new DataAnnotationsValidationPlugin(),
  37. new IndeiValidationPlugin(),
  38. new ExceptionValidationPlugin(),
  39. };
  40. /// <summary>
  41. /// An ordered collection of stream plugins that can be used to customize the behavior
  42. /// of the '^' stream binding operator.
  43. /// </summary>
  44. public static readonly IList<IStreamPlugin> StreamHandlers =
  45. new List<IStreamPlugin>
  46. {
  47. new TaskStreamPlugin(),
  48. new ObservableStreamPlugin(),
  49. };
  50. private static readonly object UninitializedValue = new object();
  51. private readonly ExpressionNode _node;
  52. private object _root;
  53. private IDisposable _rootSubscription;
  54. private WeakReference<object> _value;
  55. /// <summary>
  56. /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
  57. /// </summary>
  58. /// <param name="root">The root object.</param>
  59. /// <param name="node">The expression.</param>
  60. /// <param name="description">
  61. /// A description of the expression.
  62. /// </param>
  63. public ExpressionObserver(
  64. object root,
  65. ExpressionNode node,
  66. string description = null)
  67. {
  68. if (root == AvaloniaProperty.UnsetValue)
  69. {
  70. root = null;
  71. }
  72. _node = node;
  73. Description = description;
  74. _root = new WeakReference(root);
  75. }
  76. /// <summary>
  77. /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
  78. /// </summary>
  79. /// <param name="rootObservable">An observable which provides the root object.</param>
  80. /// <param name="node">The expression.</param>
  81. /// <param name="description">
  82. /// A description of the expression.
  83. /// </param>
  84. public ExpressionObserver(
  85. IObservable<object> rootObservable,
  86. ExpressionNode node,
  87. string description)
  88. {
  89. Contract.Requires<ArgumentNullException>(rootObservable != null);
  90. _node = node;
  91. Description = description;
  92. _root = rootObservable;
  93. }
  94. /// <summary>
  95. /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
  96. /// </summary>
  97. /// <param name="rootGetter">A function which gets the root object.</param>
  98. /// <param name="node">The expression.</param>
  99. /// <param name="update">An observable which triggers a re-read of the getter.</param>
  100. /// <param name="description">
  101. /// A description of the expression.
  102. /// </param>
  103. public ExpressionObserver(
  104. Func<object> rootGetter,
  105. ExpressionNode node,
  106. IObservable<Unit> update,
  107. string description)
  108. {
  109. Contract.Requires<ArgumentNullException>(rootGetter != null);
  110. Contract.Requires<ArgumentNullException>(update != null);
  111. Description = description;
  112. _node = node;
  113. _node.Target = new WeakReference(rootGetter());
  114. _root = update.Select(x => rootGetter());
  115. }
  116. /// <summary>
  117. /// Creates a new instance of the <see cref="ExpressionObserver"/> class.
  118. /// </summary>
  119. /// <param name="root">The root object.</param>
  120. /// <param name="expression">The expression.</param>
  121. /// <param name="enableDataValidation">Whether or not to track data validation</param>
  122. /// <param name="description">
  123. /// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
  124. /// </param>
  125. public static ExpressionObserver Create<T, U>(
  126. T root,
  127. Expression<Func<T, U>> expression,
  128. bool enableDataValidation = false,
  129. string description = null)
  130. {
  131. return new ExpressionObserver(root, Parse(expression, enableDataValidation), description ?? expression.ToString());
  132. }
  133. /// <summary>
  134. /// Creates a new instance of the <see cref="ExpressionObserver"/> class.
  135. /// </summary>
  136. /// <param name="rootObservable">An observable which provides the root object.</param>
  137. /// <param name="expression">The expression.</param>
  138. /// <param name="enableDataValidation">Whether or not to track data validation</param>
  139. /// <param name="description">
  140. /// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
  141. /// </param>
  142. public static ExpressionObserver Create<T, U>(
  143. IObservable<T> rootObservable,
  144. Expression<Func<T, U>> expression,
  145. bool enableDataValidation = false,
  146. string description = null)
  147. {
  148. Contract.Requires<ArgumentNullException>(rootObservable != null);
  149. return new ExpressionObserver(
  150. rootObservable.Select(o => (object)o),
  151. Parse(expression, enableDataValidation),
  152. description ?? expression.ToString());
  153. }
  154. /// <summary>
  155. /// Creates a new instance of the <see cref="ExpressionObserver"/> class.
  156. /// </summary>
  157. /// <param name="rootGetter">A function which gets the root object.</param>
  158. /// <param name="expression">The expression.</param>
  159. /// <param name="update">An observable which triggers a re-read of the getter.</param>
  160. /// <param name="enableDataValidation">Whether or not to track data validation</param>
  161. /// <param name="description">
  162. /// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
  163. /// </param>
  164. public static ExpressionObserver Create<T, U>(
  165. Func<T> rootGetter,
  166. Expression<Func<T, U>> expression,
  167. IObservable<Unit> update,
  168. bool enableDataValidation = false,
  169. string description = null)
  170. {
  171. Contract.Requires<ArgumentNullException>(rootGetter != null);
  172. return new ExpressionObserver(
  173. () => rootGetter(),
  174. Parse(expression, enableDataValidation),
  175. update,
  176. description ?? expression.ToString());
  177. }
  178. /// <summary>
  179. /// Attempts to set the value of a property expression.
  180. /// </summary>
  181. /// <param name="value">The value to set.</param>
  182. /// <param name="priority">The binding priority to use.</param>
  183. /// <returns>
  184. /// True if the value could be set; false if the expression does not evaluate to a
  185. /// property. Note that the <see cref="ExpressionObserver"/> must be subscribed to
  186. /// before setting the target value can work, as setting the value requires the
  187. /// expression to be evaluated.
  188. /// </returns>
  189. public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue)
  190. {
  191. if (Leaf is SettableNode settable)
  192. {
  193. var node = _node;
  194. while (node != null)
  195. {
  196. if (node is ITransformNode transform)
  197. {
  198. value = transform.Transform(value);
  199. if (value is BindingNotification)
  200. {
  201. return false;
  202. }
  203. }
  204. node = node.Next;
  205. }
  206. return settable.SetTargetValue(value, priority);
  207. }
  208. return false;
  209. }
  210. /// <summary>
  211. /// Gets a description of the expression being observed.
  212. /// </summary>
  213. public string Description { get; }
  214. /// <summary>
  215. /// Gets the expression being observed.
  216. /// </summary>
  217. public string Expression { get; }
  218. /// <summary>
  219. /// Gets the type of the expression result or null if the expression could not be
  220. /// evaluated.
  221. /// </summary>
  222. public Type ResultType => (Leaf as SettableNode)?.PropertyType;
  223. /// <summary>
  224. /// Gets the leaf node.
  225. /// </summary>
  226. private ExpressionNode Leaf
  227. {
  228. get
  229. {
  230. var node = _node;
  231. while (node.Next != null) node = node.Next;
  232. return node;
  233. }
  234. }
  235. protected override void Initialize()
  236. {
  237. _value = null;
  238. _node.Subscribe(ValueChanged);
  239. StartRoot();
  240. }
  241. protected override void Deinitialize()
  242. {
  243. _rootSubscription?.Dispose();
  244. _rootSubscription = null;
  245. _node.Unsubscribe();
  246. }
  247. protected override void Subscribed(IObserver<object> observer, bool first)
  248. {
  249. if (!first && _value != null && _value.TryGetTarget(out var value))
  250. {
  251. observer.OnNext(value);
  252. }
  253. }
  254. private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation)
  255. {
  256. return ExpressionTreeParser.Parse(expression, enableDataValidation);
  257. }
  258. private void StartRoot()
  259. {
  260. if (_root is IObservable<object> observable)
  261. {
  262. _rootSubscription = observable.Subscribe(
  263. x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
  264. x => PublishCompleted(),
  265. () => PublishCompleted());
  266. }
  267. else
  268. {
  269. _node.Target = (WeakReference)_root;
  270. }
  271. }
  272. private void ValueChanged(object value)
  273. {
  274. var broken = BindingNotification.ExtractError(value) as MarkupBindingChainException;
  275. broken?.Commit(Description);
  276. _value = new WeakReference<object>(value);
  277. PublishNext(value);
  278. }
  279. }
  280. }