A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

370 lines
14 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Parsers;
using Avalonia.Markup.Parsers.Nodes;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
public class CompiledBindingPath
{
private readonly List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
public CompiledBindingPath() { }
internal CompiledBindingPath(IEnumerable<ICompiledBindingPathElement> bindingPath, object rawSource)
{
_elements = new List<ICompiledBindingPathElement>(bindingPath);
RawSource = rawSource;
}
public ExpressionNode BuildExpression(bool enableValidation)
{
ExpressionNode pathRoot = null;
ExpressionNode path = null;
foreach (var element in _elements)
{
ExpressionNode node = null;
switch (element)
{
case NotExpressionPathElement _:
node = new LogicalNotNode();
break;
case PropertyElement prop:
node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory));
break;
case IMethodAsCommandElement methodAsCommand:
node = new PropertyAccessorNode(methodAsCommand.ExecuteMethod.Name, enableValidation, new CommandAccessorPlugin(methodAsCommand.CreateAccessor));
break;
case MethodAsDelegateElement methodAsDelegate:
node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType));
break;
case ArrayElementPathElement arr:
node = new PropertyAccessorNode(CommonPropertyNames.IndexerName, enableValidation, new ArrayElementPlugin(arr.Indices, arr.ElementType));
break;
case VisualAncestorPathElement visualAncestor:
node = new FindVisualAncestorNode(visualAncestor.AncestorType, visualAncestor.Level);
break;
case AncestorPathElement ancestor:
node = new FindAncestorNode(ancestor.AncestorType, ancestor.Level);
break;
case SelfPathElement _:
node = new SelfNode();
break;
case ElementNameElement name:
node = new ElementNameNode(name.NameScope, name.Name);
break;
case IStronglyTypedStreamElement stream:
node = new StreamNode(stream.CreatePlugin());
break;
case ITypeCastElement typeCast:
node = new StrongTypeCastNode(typeCast.Type, typeCast.Cast);
break;
default:
throw new InvalidOperationException($"Unknown binding path element type {element.GetType().FullName}");
}
path = pathRoot is null ? (pathRoot = node) : path.Next = node;
}
return pathRoot ?? new EmptyExpressionNode();
}
internal SourceMode SourceMode => _elements.Count > 0 && _elements[0] is IControlSourceBindingPathElement ? SourceMode.Control : SourceMode.Data;
internal object RawSource { get; }
public override string ToString()
=> string.Concat(_elements);
}
public class CompiledBindingPathBuilder
{
private object _rawSource;
private List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
public CompiledBindingPathBuilder Not()
{
_elements.Add(new NotExpressionPathElement());
return this;
}
public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
{
_elements.Add(new PropertyElement(info, accessorFactory, _elements.Count == 0));
return this;
}
public CompiledBindingPathBuilder Method(RuntimeMethodHandle handle, RuntimeTypeHandle delegateType)
{
_elements.Add(new MethodAsDelegateElement(handle, delegateType));
return this;
}
public CompiledBindingPathBuilder Command(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties)
{
_elements.Add(new MethodAsCommandElement(executeMethod, canExecuteMethod, dependsOnProperties));
return this;
}
public CompiledBindingPathBuilder CommandWithParameter<T>(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties)
{
_elements.Add(new MethodAsCommandElement<T>(executeMethod, canExecuteMethod, dependsOnProperties));
return this;
}
public CompiledBindingPathBuilder StreamTask<T>()
{
_elements.Add(new TaskStreamPathElement<T>());
return this;
}
public CompiledBindingPathBuilder StreamObservable<T>()
{
_elements.Add(new ObservableStreamPathElement<T>());
return this;
}
public CompiledBindingPathBuilder Self()
{
_elements.Add(new SelfPathElement());
return this;
}
public CompiledBindingPathBuilder Ancestor(Type ancestorType, int level)
{
_elements.Add(new AncestorPathElement(ancestorType, level));
return this;
}
public CompiledBindingPathBuilder VisualAncestor(Type ancestorType, int level)
{
_elements.Add(new VisualAncestorPathElement(ancestorType, level));
return this;
}
public CompiledBindingPathBuilder ElementName(INameScope nameScope, string name)
{
_elements.Add(new ElementNameElement(nameScope, name));
return this;
}
public CompiledBindingPathBuilder ArrayElement(int[] indices, Type elementType)
{
_elements.Add(new ArrayElementPathElement(indices, elementType));
return this;
}
public CompiledBindingPathBuilder TypeCast<T>()
{
_elements.Add(new TypeCastPathElement<T>());
return this;
}
public CompiledBindingPathBuilder SetRawSource(object rawSource)
{
_rawSource = rawSource;
return this;
}
public CompiledBindingPath Build() => new CompiledBindingPath(_elements, _rawSource);
}
public interface ICompiledBindingPathElement
{
}
internal interface IControlSourceBindingPathElement { }
internal class NotExpressionPathElement : ICompiledBindingPathElement
{
public static readonly NotExpressionPathElement Instance = new NotExpressionPathElement();
}
internal class PropertyElement : ICompiledBindingPathElement
{
private readonly bool _isFirstElement;
public PropertyElement(IPropertyInfo property, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory, bool isFirstElement)
{
Property = property;
AccessorFactory = accessorFactory;
_isFirstElement = isFirstElement;
}
public IPropertyInfo Property { get; }
public Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
public override string ToString()
=> _isFirstElement ? Property.Name : $".{Property.Name}";
}
internal class MethodAsDelegateElement : ICompiledBindingPathElement
{
public MethodAsDelegateElement(RuntimeMethodHandle method, RuntimeTypeHandle delegateType)
{
Method = (MethodInfo)MethodBase.GetMethodFromHandle(method);
DelegateType = Type.GetTypeFromHandle(delegateType);
}
public MethodInfo Method { get; }
public Type DelegateType { get; }
}
internal interface IMethodAsCommandElement
{
MethodInfo ExecuteMethod { get; }
IPropertyAccessor CreateAccessor(WeakReference<object> obj);
}
internal class MethodAsCommandElement : ICompiledBindingPathElement, IMethodAsCommandElement
{
public MethodAsCommandElement(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnElements)
{
ExecuteMethod = (MethodInfo)MethodBase.GetMethodFromHandle(executeMethod);
CanExecuteMethod = canExecuteMethod != default ? (MethodInfo)MethodBase.GetMethodFromHandle(canExecuteMethod) : null;
DependsOnProperties = new HashSet<string>(dependsOnElements);
}
public MethodInfo ExecuteMethod { get; }
public MethodInfo CanExecuteMethod { get; }
public HashSet<string> DependsOnProperties { get; }
public IPropertyAccessor CreateAccessor(WeakReference<object> obj)
{
obj.TryGetTarget(out object target);
return new CommandAccessorPlugin.CommandWithoutParameterAccessor(obj, DependsOnProperties, (Action)ExecuteMethod.CreateDelegate(typeof(Action), target), (Func<object, bool>)CanExecuteMethod?.CreateDelegate(typeof(Func<object, bool>), target));
}
}
internal class MethodAsCommandElement<T> : ICompiledBindingPathElement, IMethodAsCommandElement
{
public MethodAsCommandElement(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnElements)
{
ExecuteMethod = (MethodInfo)MethodBase.GetMethodFromHandle(executeMethod);
CanExecuteMethod = canExecuteMethod != default ? (MethodInfo)MethodBase.GetMethodFromHandle(canExecuteMethod) : null;
DependsOnProperties = new HashSet<string>(dependsOnElements);
}
public MethodInfo ExecuteMethod { get; }
public MethodInfo CanExecuteMethod { get; }
public HashSet<string> DependsOnProperties { get; }
public IPropertyAccessor CreateAccessor(WeakReference<object> obj)
{
obj.TryGetTarget(out object target);
return new CommandAccessorPlugin.CommandWithParameterAccessor<T>(obj, DependsOnProperties, (Action<T>)ExecuteMethod.CreateDelegate(typeof(Action<T>), target), (Func<object, bool>)CanExecuteMethod?.CreateDelegate(typeof(Func<object, bool>), target));
}
}
internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement
{
IStreamPlugin CreatePlugin();
}
internal interface ITypeCastElement : ICompiledBindingPathElement
{
Type Type { get; }
Func<object, object> Cast { get; }
}
internal class TaskStreamPathElement<T> : IStronglyTypedStreamElement
{
public static readonly TaskStreamPathElement<T> Instance = new TaskStreamPathElement<T>();
public IStreamPlugin CreatePlugin() => new TaskStreamPlugin<T>();
}
internal class ObservableStreamPathElement<T> : IStronglyTypedStreamElement
{
public static readonly ObservableStreamPathElement<T> Instance = new ObservableStreamPathElement<T>();
public IStreamPlugin CreatePlugin() => new ObservableStreamPlugin<T>();
}
internal class SelfPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
{
public static readonly SelfPathElement Instance = new SelfPathElement();
public override string ToString()
=> "$self";
}
internal class AncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
{
public AncestorPathElement(Type ancestorType, int level)
{
AncestorType = ancestorType;
Level = level;
}
public Type AncestorType { get; }
public int Level { get; }
public override string ToString()
=> $"$parent[{AncestorType?.Name},{Level}]";
}
internal class VisualAncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
{
public VisualAncestorPathElement(Type ancestorType, int level)
{
AncestorType = ancestorType;
Level = level;
}
public Type AncestorType { get; }
public int Level { get; }
}
internal class ElementNameElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
{
public ElementNameElement(INameScope nameScope, string name)
{
NameScope = nameScope;
Name = name;
}
public INameScope NameScope { get; }
public string Name { get; }
public override string ToString()
=> $"#{Name}";
}
internal class ArrayElementPathElement : ICompiledBindingPathElement
{
public ArrayElementPathElement(int[] indices, Type elementType)
{
Indices = indices;
ElementType = elementType;
}
public int[] Indices { get; }
public Type ElementType { get; }
public override string ToString()
=> $"[{string.Join(",", Indices)}]";
}
internal class TypeCastPathElement<T> : ITypeCastElement
{
private static object TryCast(object obj)
{
if (obj is T result)
return result;
return null;
}
public Type Type => typeof(T);
public Func<object, object> Cast => TryCast;
public override string ToString()
=> $"({Type.FullName})";
}
}