Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Text;
using Avalonia.Reactive;

namespace Avalonia.Data.Core
{
public class AvaloniaPropertyAccessorNode : SettableNode
{
private IDisposable _subscription;
private readonly bool _enableValidation;
private readonly AvaloniaProperty _property;

public AvaloniaPropertyAccessorNode(AvaloniaProperty property, bool enableValidation)
{
_property = property;
_enableValidation = enableValidation;
}

public override string Description => PropertyName;
public string PropertyName { get; }
public override Type PropertyType => _property.PropertyType;

protected override bool SetTargetValueCore(object value, BindingPriority priority)
{
try
{
if (Target.IsAlive && Target.Target is IAvaloniaObject obj)
{
obj.SetValue(_property, value, priority);
return true;
}
return false;
}
catch
{
return false;
}
}

protected override void StartListeningCore(WeakReference reference)
{
if (reference.Target is IAvaloniaObject obj)
{
_subscription = new AvaloniaPropertyObservable<object>(obj, _property).Subscribe(ValueChanged);
}
else
{
_subscription = null;
}
}

protected override void StopListeningCore()
{
_subscription?.Dispose();
_subscription = null;
}
}
}
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Avalonia.Data.Core
{
internal class EmptyExpressionNode : ExpressionNode
public class EmptyExpressionNode : ExpressionNode
{
public override string Description => ".";
}
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Data/Core/ExpressionNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Avalonia.Data.Core
{
internal abstract class ExpressionNode
public abstract class ExpressionNode
{
private static readonly object CacheInvalid = new object();
protected static readonly WeakReference UnsetReference =
Expand Down
30 changes: 0 additions & 30 deletions src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs

This file was deleted.

130 changes: 89 additions & 41 deletions src/Avalonia.Base/Data/Core/ExpressionObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Data.Core.Parsers;
using Avalonia.Data.Core.Plugins;
using Avalonia.Reactive;

Expand Down Expand Up @@ -61,82 +63,135 @@ public class ExpressionObserver : LightweightObservableBase<object>, IDescriptio
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="root">The root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <param name="node">The expression.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/> will be used.
/// A description of the expression.
/// </param>
public ExpressionObserver(
object root,
string expression,
bool enableDataValidation = false,
ExpressionNode node,
string description = null)
{
Contract.Requires<ArgumentNullException>(expression != null);

if (root == AvaloniaProperty.UnsetValue)
{
root = null;
}

Expression = expression;
Description = description ?? expression;
_node = Parse(expression, enableDataValidation);
_node = node;
Description = description;
_root = new WeakReference(root);
}

/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootObservable">An observable which provides the root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <param name="node">The expression.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/> will be used.
/// A description of the expression.
/// </param>
public ExpressionObserver(
IObservable<object> rootObservable,
string expression,
bool enableDataValidation = false,
string description = null)
ExpressionNode node,
string description)
{
Contract.Requires<ArgumentNullException>(rootObservable != null);
Contract.Requires<ArgumentNullException>(expression != null);

Expression = expression;
Description = description ?? expression;
_node = Parse(expression, enableDataValidation);

_node = node;
Description = description;
_root = rootObservable;
}

/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootGetter">A function which gets the root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="node">The expression.</param>
/// <param name="update">An observable which triggers a re-read of the getter.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/> will be used.
/// A description of the expression.
/// </param>
public ExpressionObserver(
Func<object> rootGetter,
string expression,
ExpressionNode node,
IObservable<Unit> update,
bool enableDataValidation = false,
string description = null)
string description)
{
Contract.Requires<ArgumentNullException>(rootGetter != null);
Contract.Requires<ArgumentNullException>(expression != null);
Contract.Requires<ArgumentNullException>(update != null);

Expression = expression;
Description = description ?? expression;
_node = Parse(expression, enableDataValidation);
Description = description;
_node = node;
_node.Target = new WeakReference(rootGetter());
_root = update.Select(x => rootGetter());
}


/// <summary>
/// Creates a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="root">The root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether or not to track data validation</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
public static ExpressionObserver Create<T, U>(
T root,
Expression<Func<T, U>> expression,
bool enableDataValidation = false,
string description = null)
{
return new ExpressionObserver(root, Parse(expression, enableDataValidation), description ?? expression.ToString());
}

/// <summary>
/// Creates a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootObservable">An observable which provides the root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether or not to track data validation</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
public static ExpressionObserver Create<T, U>(
IObservable<T> rootObservable,
Expression<Func<T, U>> expression,
bool enableDataValidation = false,
string description = null)
{
Contract.Requires<ArgumentNullException>(rootObservable != null);
return new ExpressionObserver(
rootObservable.Select(o => (object)o),
Parse(expression, enableDataValidation),
description ?? expression.ToString());
}

/// <summary>
/// Creates a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootGetter">A function which gets the root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="update">An observable which triggers a re-read of the getter.</param>
/// <param name="enableDataValidation">Whether or not to track data validation</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
public static ExpressionObserver Create<T, U>(
Func<T> rootGetter,
Expression<Func<T, U>> expression,
IObservable<Unit> update,
bool enableDataValidation = false,
string description = null)
{
Contract.Requires<ArgumentNullException>(rootGetter != null);

return new ExpressionObserver(
() => rootGetter(),
Parse(expression, enableDataValidation),
update,
description ?? expression.ToString());
}

/// <summary>
/// Attempts to set the value of a property expression.
/// </summary>
Expand Down Expand Up @@ -221,16 +276,9 @@ protected override void Subscribed(IObserver<object> observer, bool first)
}
}

private static ExpressionNode Parse(string expression, bool enableDataValidation)
private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation)
{
if (!string.IsNullOrWhiteSpace(expression))
{
return ExpressionNodeBuilder.Build(expression, enableDataValidation);
}
else
{
return new EmptyExpressionNode();
}
return ExpressionTreeParser.Parse(expression, enableDataValidation);
}

private void StartRoot()
Expand Down
4 changes: 2 additions & 2 deletions src/Avalonia.Base/Data/Core/ExpressionParseException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public class ExpressionParseException : Exception
/// </summary>
/// <param name="column">The column position of the error.</param>
/// <param name="message">The exception message.</param>
public ExpressionParseException(int column, string message)
: base(message)
public ExpressionParseException(int column, string message, Exception innerException = null)
: base(message, innerException)
{
Column = column;
}
Expand Down
71 changes: 71 additions & 0 deletions src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Avalonia.Data;

namespace Avalonia.Data.Core
{
class IndexerExpressionNode : IndexerNodeBase
{
private readonly ParameterExpression _parameter;
private readonly IndexExpression _expression;
private readonly Delegate _setDelegate;
private readonly Delegate _getDelegate;
private readonly Delegate _firstArgumentDelegate;

public IndexerExpressionNode(IndexExpression expression)
{
_parameter = Expression.Parameter(expression.Object.Type);
_expression = expression.Update(_parameter, expression.Arguments);

_getDelegate = Expression.Lambda(_expression, _parameter).Compile();

var valueParameter = Expression.Parameter(expression.Type);

_setDelegate = Expression.Lambda(Expression.Assign(_expression, valueParameter), _parameter, valueParameter).Compile();

_firstArgumentDelegate = Expression.Lambda(_expression.Arguments[0], _parameter).Compile();
}

public override Type PropertyType => _expression.Type;

public override string Description => _expression.ToString();

protected override bool SetTargetValueCore(object value, BindingPriority priority)
{
try
{
_setDelegate.DynamicInvoke(Target.Target, value);
return true;
}
catch (Exception)
{
return false;
}
}

protected override object GetValue(object target)
{
try
{
return _getDelegate.DynamicInvoke(target);
}
catch (TargetInvocationException e) when (e.InnerException is ArgumentOutOfRangeException
|| e.InnerException is IndexOutOfRangeException
|| e.InnerException is KeyNotFoundException)
{
return AvaloniaProperty.UnsetValue;
}
}

protected override bool ShouldUpdate(object sender, PropertyChangedEventArgs e)
{
return _expression.Indexer == null || _expression.Indexer.Name == e.PropertyName;
}

protected override int? TryGetFirstArgumentAsInt() => _firstArgumentDelegate.DynamicInvoke(Target.Target) as int?;
}
}
Loading