Browse Source
`DataAnnotationsValidationPlugin` is public and so it can't be moved. No point in moving the others if this one will be in the wrong place.pull/13970/head
18 changed files with 228 additions and 233 deletions
@ -1,27 +1,91 @@ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Data.Core.Plugins; |
|||
|
|||
internal class ObservableStreamPlugin<T> : IStreamPlugin |
|||
namespace Avalonia.Data.Core.Plugins |
|||
{ |
|||
public bool Match(WeakReference<object?> reference) |
|||
/// <summary>
|
|||
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
|
|||
/// </summary>
|
|||
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] |
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
internal class ObservableStreamPlugin : IStreamPlugin |
|||
{ |
|||
return reference.TryGetTarget(out var target) && target is IObservable<T>; |
|||
} |
|||
private static MethodInfo? s_observableGeneric; |
|||
private static MethodInfo? s_observableSelect; |
|||
|
|||
public IObservable<object?> Start(WeakReference<object?> reference) |
|||
{ |
|||
if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs)) |
|||
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] |
|||
public ObservableStreamPlugin() |
|||
{ |
|||
return Observable.Empty<object?>(); |
|||
|
|||
} |
|||
else if (target is IObservable<object?> obj) |
|||
|
|||
/// <summary>
|
|||
/// Checks whether this plugin handles the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the value.</param>
|
|||
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
|
|||
public virtual bool Match(WeakReference<object?> reference) |
|||
{ |
|||
return obj; |
|||
reference.TryGetTarget(out var target); |
|||
|
|||
return target != null && target.GetType().GetInterfaces().Any(x => |
|||
x.IsGenericType && |
|||
x.GetGenericTypeDefinition() == typeof(IObservable<>)); |
|||
} |
|||
|
|||
return obs.Select(x => (object?)x); |
|||
/// <summary>
|
|||
/// Starts producing output based on the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <returns>
|
|||
/// An observable that produces the output for the value.
|
|||
/// </returns>
|
|||
public virtual IObservable<object?> Start(WeakReference<object?> reference) |
|||
{ |
|||
if (!reference.TryGetTarget(out var target) || target is null) |
|||
return Observable.Empty<object?>(); |
|||
|
|||
// If the observable returns a reference type then we can cast it.
|
|||
if (target is IObservable<object?> result) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
// If the observable returns a value type then we need to call Observable.Select on it.
|
|||
// First get the type of T in `IObservable<T>`.
|
|||
var sourceType = target.GetType().GetInterfaces().First(x => |
|||
x.IsGenericType && |
|||
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0]; |
|||
|
|||
// Get the BoxObservable<T> method.
|
|||
var select = GetBoxObservable(sourceType); |
|||
|
|||
// Call BoxObservable(target);
|
|||
return (IObservable<object?>)select.Invoke( |
|||
null, |
|||
new[] { target })!; |
|||
} |
|||
|
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
private static MethodInfo GetBoxObservable(Type source) |
|||
{ |
|||
return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source); |
|||
} |
|||
|
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
private static MethodInfo GetBoxObservable() |
|||
{ |
|||
return s_observableSelect |
|||
??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic) |
|||
?? throw new InvalidOperationException("BoxObservable method was not found."); |
|||
} |
|||
|
|||
private static IObservable<object?> BoxObservable<T>(IObservable<T> source) |
|||
{ |
|||
return source.Select(v => (object?)v); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Data.Core.Plugins; |
|||
|
|||
internal class ObservableStreamPlugin<T> : IStreamPlugin |
|||
{ |
|||
public bool Match(WeakReference<object?> reference) |
|||
{ |
|||
return reference.TryGetTarget(out var target) && target is IObservable<T>; |
|||
} |
|||
|
|||
public IObservable<object?> Start(WeakReference<object?> reference) |
|||
{ |
|||
if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs)) |
|||
{ |
|||
return Observable.Empty<object?>(); |
|||
} |
|||
else if (target is IObservable<object?> obj) |
|||
{ |
|||
return obj; |
|||
} |
|||
|
|||
return obs.Select(x => (object?)x); |
|||
} |
|||
} |
|||
@ -1,91 +0,0 @@ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Data.Core.Plugins.Reflection |
|||
{ |
|||
/// <summary>
|
|||
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
|
|||
/// </summary>
|
|||
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] |
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
internal class ObservableStreamPlugin : IStreamPlugin |
|||
{ |
|||
private static MethodInfo? s_observableGeneric; |
|||
private static MethodInfo? s_observableSelect; |
|||
|
|||
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] |
|||
public ObservableStreamPlugin() |
|||
{ |
|||
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks whether this plugin handles the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the value.</param>
|
|||
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
|
|||
public virtual bool Match(WeakReference<object?> reference) |
|||
{ |
|||
reference.TryGetTarget(out var target); |
|||
|
|||
return target != null && target.GetType().GetInterfaces().Any(x => |
|||
x.IsGenericType && |
|||
x.GetGenericTypeDefinition() == typeof(IObservable<>)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Starts producing output based on the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <returns>
|
|||
/// An observable that produces the output for the value.
|
|||
/// </returns>
|
|||
public virtual IObservable<object?> Start(WeakReference<object?> reference) |
|||
{ |
|||
if (!reference.TryGetTarget(out var target) || target is null) |
|||
return Observable.Empty<object?>(); |
|||
|
|||
// If the observable returns a reference type then we can cast it.
|
|||
if (target is IObservable<object?> result) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
// If the observable returns a value type then we need to call Observable.Select on it.
|
|||
// First get the type of T in `IObservable<T>`.
|
|||
var sourceType = target.GetType().GetInterfaces().First(x => |
|||
x.IsGenericType && |
|||
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0]; |
|||
|
|||
// Get the BoxObservable<T> method.
|
|||
var select = GetBoxObservable(sourceType); |
|||
|
|||
// Call BoxObservable(target);
|
|||
return (IObservable<object?>)select.Invoke( |
|||
null, |
|||
new[] { target })!; |
|||
} |
|||
|
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
private static MethodInfo GetBoxObservable(Type source) |
|||
{ |
|||
return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source); |
|||
} |
|||
|
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
private static MethodInfo GetBoxObservable() |
|||
{ |
|||
return s_observableSelect |
|||
??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic) |
|||
?? throw new InvalidOperationException("BoxObservable method was not found."); |
|||
} |
|||
|
|||
private static IObservable<object?> BoxObservable<T>(IObservable<T> source) |
|||
{ |
|||
return source.Select(v => (object?)v); |
|||
} |
|||
} |
|||
} |
|||
@ -1,85 +0,0 @@ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Data.Core.Plugins.Reflection |
|||
{ |
|||
/// <summary>
|
|||
/// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.
|
|||
/// </summary>
|
|||
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] |
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
internal class TaskStreamPlugin : IStreamPlugin |
|||
{ |
|||
/// <summary>
|
|||
/// Checks whether this plugin handles the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the value.</param>
|
|||
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
|
|||
public virtual bool Match(WeakReference<object?> reference) |
|||
{ |
|||
reference.TryGetTarget(out var target); |
|||
|
|||
return target is Task; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Starts producing output based on the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <returns>
|
|||
/// An observable that produces the output for the value.
|
|||
/// </returns>
|
|||
public virtual IObservable<object?> Start(WeakReference<object?> reference) |
|||
{ |
|||
reference.TryGetTarget(out var target); |
|||
|
|||
if (target is Task task) |
|||
{ |
|||
var resultProperty = task.GetType().GetRuntimeProperty("Result"); |
|||
|
|||
if (resultProperty != null) |
|||
{ |
|||
switch (task.Status) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
case TaskStatus.Faulted: |
|||
return HandleCompleted(task); |
|||
default: |
|||
var subject = new LightweightSubject<object?>(); |
|||
task.ContinueWith( |
|||
x => HandleCompleted(task).Subscribe(subject), |
|||
TaskScheduler.FromCurrentSynchronizationContext()) |
|||
.ConfigureAwait(false); |
|||
return subject; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return Observable.Empty<object?>(); |
|||
} |
|||
|
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
private static IObservable<object?> HandleCompleted(Task task) |
|||
{ |
|||
var resultProperty = task.GetType().GetRuntimeProperty("Result"); |
|||
|
|||
if (resultProperty != null) |
|||
{ |
|||
switch (task.Status) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
return Observable.Return(resultProperty.GetValue(task)); |
|||
case TaskStatus.Faulted: |
|||
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error)); |
|||
default: |
|||
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); |
|||
} |
|||
} |
|||
|
|||
return Observable.Empty<object>(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,49 +1,85 @@ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Data.Core.Plugins; |
|||
|
|||
internal class TaskStreamPlugin<T> : IStreamPlugin |
|||
namespace Avalonia.Data.Core.Plugins |
|||
{ |
|||
public bool Match(WeakReference<object?> reference) |
|||
/// <summary>
|
|||
/// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.
|
|||
/// </summary>
|
|||
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] |
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
internal class TaskStreamPlugin : IStreamPlugin |
|||
{ |
|||
return reference.TryGetTarget(out var target) && target is Task<T>; |
|||
} |
|||
/// <summary>
|
|||
/// Checks whether this plugin handles the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the value.</param>
|
|||
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
|
|||
public virtual bool Match(WeakReference<object?> reference) |
|||
{ |
|||
reference.TryGetTarget(out var target); |
|||
|
|||
public IObservable<object?> Start(WeakReference<object?> reference) |
|||
{ |
|||
if (!(reference.TryGetTarget(out var target) && target is Task<T> task)) |
|||
return target is Task; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Starts producing output based on the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <returns>
|
|||
/// An observable that produces the output for the value.
|
|||
/// </returns>
|
|||
public virtual IObservable<object?> Start(WeakReference<object?> reference) |
|||
{ |
|||
reference.TryGetTarget(out var target); |
|||
|
|||
if (target is Task task) |
|||
{ |
|||
var resultProperty = task.GetType().GetRuntimeProperty("Result"); |
|||
|
|||
if (resultProperty != null) |
|||
{ |
|||
switch (task.Status) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
case TaskStatus.Faulted: |
|||
return HandleCompleted(task); |
|||
default: |
|||
var subject = new LightweightSubject<object?>(); |
|||
task.ContinueWith( |
|||
x => HandleCompleted(task).Subscribe(subject), |
|||
TaskScheduler.FromCurrentSynchronizationContext()) |
|||
.ConfigureAwait(false); |
|||
return subject; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return Observable.Empty<object?>(); |
|||
} |
|||
|
|||
switch (task.Status) |
|||
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] |
|||
private static IObservable<object?> HandleCompleted(Task task) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
case TaskStatus.Faulted: |
|||
return HandleCompleted(task); |
|||
default: |
|||
var subject = new LightweightSubject<object?>(); |
|||
task.ContinueWith( |
|||
_ => HandleCompleted(task).Subscribe(subject), |
|||
TaskScheduler.FromCurrentSynchronizationContext()) |
|||
.ConfigureAwait(false); |
|||
return subject; |
|||
} |
|||
} |
|||
var resultProperty = task.GetType().GetRuntimeProperty("Result"); |
|||
|
|||
if (resultProperty != null) |
|||
{ |
|||
switch (task.Status) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
return Observable.Return(resultProperty.GetValue(task)); |
|||
case TaskStatus.Faulted: |
|||
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error)); |
|||
default: |
|||
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); |
|||
} |
|||
} |
|||
|
|||
private static IObservable<object?> HandleCompleted(Task<T> task) |
|||
{ |
|||
switch (task.Status) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
return Observable.Return((object?)task.Result); |
|||
case TaskStatus.Faulted: |
|||
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error)); |
|||
default: |
|||
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); |
|||
return Observable.Empty<object>(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,49 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Data.Core.Plugins; |
|||
|
|||
internal class TaskStreamPlugin<T> : IStreamPlugin |
|||
{ |
|||
public bool Match(WeakReference<object?> reference) |
|||
{ |
|||
return reference.TryGetTarget(out var target) && target is Task<T>; |
|||
} |
|||
|
|||
public IObservable<object?> Start(WeakReference<object?> reference) |
|||
{ |
|||
if (!(reference.TryGetTarget(out var target) && target is Task<T> task)) |
|||
{ |
|||
return Observable.Empty<object?>(); |
|||
} |
|||
|
|||
switch (task.Status) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
case TaskStatus.Faulted: |
|||
return HandleCompleted(task); |
|||
default: |
|||
var subject = new LightweightSubject<object?>(); |
|||
task.ContinueWith( |
|||
_ => HandleCompleted(task).Subscribe(subject), |
|||
TaskScheduler.FromCurrentSynchronizationContext()) |
|||
.ConfigureAwait(false); |
|||
return subject; |
|||
} |
|||
} |
|||
|
|||
|
|||
private static IObservable<object?> HandleCompleted(Task<T> task) |
|||
{ |
|||
switch (task.Status) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
return Observable.Return((object?)task.Result); |
|||
case TaskStatus.Faulted: |
|||
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error)); |
|||
default: |
|||
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue