using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using JetBrains.Annotations;
using JetBrains.Core;
using JetBrains.Diagnostics;
using JetBrains.Lifetimes;
using JetBrains.Rd.Base;
using JetBrains.Rd.Impl;
using JetBrains.Rd.Tasks;
using JetBrains.Util;
using JetBrains.Util.Util;
namespace JetBrains.Rd.Reflection
{
///
/// To get maximal performance of Rd framework you should directly provide serializers to every Model and Property.
///
/// Creating models by hand with this approach is tedious and error-prone. The main idea behind is to automatically create RdExt and initialize fields and properties with
/// appropriate serializers.
///
///
public class ReflectionRdActivator
{
private static readonly ILog ourLog = Log.GetLog();
private readonly ReflectionSerializers mySerializers;
private readonly IProxyGenerator myProxyGenerator;
private readonly ITypesCatalog? myTypesCatalog;
public ReflectionSerializers Serializers => mySerializers;
public IProxyGenerator Generator => myProxyGenerator;
public ITypesCatalog? TypesCatalog => myTypesCatalog;
///
/// current activation stack.
///
/// used to protect from circular dependencies only.
///
[ThreadStatic]
private static Queue? myCurrentActivationChain;
public ReflectionRdActivator(ReflectionSerializers serializers, ITypesCatalog? typesCatalog)
: this(serializers, new ProxyGenerator(), typesCatalog)
{
}
public ReflectionRdActivator(ReflectionSerializers serializers, IProxyGenerator proxyGenerator, ITypesCatalog? typesCatalog)
{
mySerializers = serializers;
myTypesCatalog = typesCatalog;
myProxyGenerator = proxyGenerator;
}
///
/// Create and bind class with
///
///
///
public T ActivateBind(Lifetime lifetime, IProtocol protocol) where T : RdBindableBase
{
var instance = Activate();
var typename = GetTypeName(typeof(T));
instance.Identify(protocol.Identities, RdId.Root.Mix(typename));
instance.BindTopLevel(lifetime, protocol, typename);
return instance;
}
///
/// Create and bind class with
///
///
[PublicAPI]
public RdExtReflectionBindableBase ActivateBind(Type type, Lifetime lifetime, IProtocol protocol)
{
var instance = Activate(type);
var typename = GetTypeName(type);
var bindable = (RdExtReflectionBindableBase) instance;
bindable.Identify(protocol.Identities, RdId.Root.Mix(typename));
bindable.BindTopLevel(lifetime, protocol, typename);
return bindable;
}
///
/// Creates and initializes reactive primitives, RdModels and RdExts.
///
public T Activate()
{
return (T) Activate(typeof(T));
}
///
/// Activate or or its members.
///
public object Activate(Type type, string name)
{
if (Mode.IsAssertion)
{
myCurrentActivationChain = myCurrentActivationChain ?? new Queue();
myCurrentActivationChain.Clear(); // clear previous attempts to activate different types
}
// We should register serializer for current type and all of it members to have possibility to get valid serializers for arguments.
myTypesCatalog?.AddType(type);
return ActivateMember(type, name).NotNull();
}
///
/// Activate or or its members.
///
public object Activate(Type type)
{
return Activate(type, "Anonymous");
}
private object ActivateRd(Type type)
{
if (Mode.IsAssertion)
{
Assertion.AssertNotNull(myCurrentActivationChain);
Assertion.Assert(!myCurrentActivationChain.Contains(type),
$"Unable to activate {type.FullName}: circular dependency detected: {string.Join(" -> ", myCurrentActivationChain.Select(t => t.FullName).ToArray())}");
myCurrentActivationChain.Enqueue(type);
}
var typeInfo = type.GetTypeInfo();
var implementingType = ReflectionSerializerVerifier.GetImplementingType(typeInfo);
if (Mode.IsAssertion) Assertion.Assert(typeof(RdBindableBase).GetTypeInfo().IsAssignableFrom(implementingType),
$"Unable to activate {type.FullName}: type should be {nameof(RdBindableBase)}");
object instance;
try
{
instance = Activator.CreateInstance(implementingType);
}
catch (MissingMethodException e)
{
throw new MissingMethodException($"Unable to create instance of: {implementingType.ToString(true)}.{e.Message}");
}
ReflectionInitInternal(instance);
if (Mode.IsAssertion)
myCurrentActivationChain!.Dequeue();
return instance;
}
public object ReflectionInit(object instance)
{
if (Mode.IsAssertion)
{
myCurrentActivationChain = myCurrentActivationChain ?? new Queue();
myCurrentActivationChain.Clear(); // clear previous attempts to activate different types
}
return ReflectionInitInternal(instance);
}
private object ReflectionInitInternal(object instance)
{
var typeInfo = instance.GetType().GetTypeInfo();
if (ReflectionSerializerVerifier.HasRdExtAttribute(instance.GetType().GetTypeInfo()))
ReflectionSerializerVerifier.AssertValidRdExt(typeInfo);
foreach (var mi in SerializerReflectionUtil.GetSerializableFields(typeInfo))
{
var currentValue = ReflectionUtil.GetGetter(mi)(instance);
if (currentValue == null)
{
currentValue = ActivateMember(ReflectionUtil.GetReturnType(mi), mi.Name);
var memberSetter = ReflectionUtil.GetSetter(mi);
memberSetter(instance, currentValue);
}
else
{
var implementingType = ReflectionSerializerVerifier.GetImplementingType(ReflectionUtil.GetReturnType(mi).GetTypeInfo());
if (Mode.IsAssertion) Assertion.Assert(currentValue.GetType() == implementingType,
"Bindable field {0} was initialized with incompatible type. Expected type {1}, actual {2}",
mi,
implementingType.ToString(true),
currentValue.GetType().ToString(true));
}
}
// Add RdEndpoint for Impl class (counterpart of Proxy)
var interfaces = typeInfo.GetInterfaces();
bool isProxy = interfaces.Contains(typeof(IProxyTypeMarker));
var rpcInterface = ReflectionSerializerVerifier.GetRpcInterface(typeInfo);
if (!isProxy && rpcInterface != null)
{
var interfaceMethods = ReflectionSerializerVerifier.GetMethodsMap(typeInfo, rpcInterface);
foreach (var interfaceMethod in interfaceMethods)
{
var adapter = myProxyGenerator.CreateAdapter(rpcInterface, interfaceMethod);
var name = ProxyGenerator.ProxyFieldName(interfaceMethod);
var requestType = ProxyGenerator.GetRequstType(interfaceMethod)[0];
EnsureFakeTupleRegistered(requestType);
var responseNonTaskType = ProxyGenerator.GetResponseType(interfaceMethod, unwrapTask: true);
var responseType = ProxyGenerator.GetResponseType(interfaceMethod, unwrapTask: false);
var endPointType = typeof(RdCall<,>).MakeGenericType(requestType, responseNonTaskType);
var endpoint = ActivateGenericMember(name, endPointType.GetTypeInfo()).NotNull();
SetAsync(endpoint);
if (endpoint is RdReactiveBase reactiveBase)
reactiveBase.ValueCanBeNull = true;
if (ProxyGenerator.IsSync(interfaceMethod))
{
var delType = typeof(Func<,,>).MakeGenericType(typeof(Lifetime), requestType, typeof(RdTask<>).MakeGenericType(responseNonTaskType));
var @delegate = adapter.CreateDelegate(delType, instance);
var methodInfo = typeof(ReflectionRdActivator).GetMethod(nameof(SetHandler)).NotNull().MakeGenericMethod(requestType, responseNonTaskType);
methodInfo.Invoke(null, new[] {endpoint, @delegate});
}
else
{
if (responseType == typeof(Task))
{
var delType = typeof(Func<,,>).MakeGenericType(typeof(Lifetime), requestType, typeof(Task));
var @delegate = adapter.CreateDelegate(delType, instance);
var methodInfo = typeof(ReflectionRdActivator).GetMethod(nameof(SetHandlerTaskVoid)).NotNull().MakeGenericMethod(requestType);
methodInfo.Invoke(null, new[] {endpoint, @delegate});
}
else
{
var delType = typeof(Func<,,>).MakeGenericType(typeof(Lifetime), requestType, typeof(Task<>).MakeGenericType(responseNonTaskType));
var @delegate = adapter.CreateDelegate(delType, instance);
var methodInfo = typeof(ReflectionRdActivator).GetMethod(nameof(SetHandlerTask)).NotNull().MakeGenericMethod(requestType, responseNonTaskType);
methodInfo.Invoke(null, new[] {endpoint, @delegate});
}
}
var bindableChildren = ((IReflectionBindable)instance).BindableChildren;
bindableChildren.Add(new KeyValuePair(name, endpoint));
}
}
else if (rpcInterface != null)
{
foreach (var interfaceMethod in rpcInterface.GetMethods())
{
var requestType = ProxyGenerator.GetRequstType(interfaceMethod)[0];
EnsureFakeTupleRegistered(requestType);
}
}
// Allow initialize to setup bindings to composite properties.
if (instance is IReflectionBindable reflectionBindable)
{
reflectionBindable.EnsureBindableChildren();
if (reflectionBindable.BindableChildren.Count == 0)
{
ourLog.Warn($"{reflectionBindable.GetType().ToString(true)} RdExt without bindable children was activated.");
}
reflectionBindable.OnActivated();
}
return instance;
}
private void EnsureFakeTupleRegistered(Type type)
{
Assertion.AssertNotNull(myTypesCatalog, "myPolymorphicTypesCatalog required to be NotNull when RPC is used");
myTypesCatalog.AddType(type);
}
///
/// Wrapper method to simplify search with overload resolution for two methods in RdEndpoint.
///
/// Used for async methods returning generic Task.
///
public static void SetHandlerTask(RdCall endpoint, Func> handler)
{
endpoint.Set(handler);
}
///
/// Wrapper method to simplify search with overload resolution for two methods in RdEndpoint.
///
/// Used for async methods returning non-generic Task.
///
public static void SetHandlerTaskVoid(RdCall endpoint, Func handler)
{
endpoint.SetRdTask((lt, req) => handler(lt, req).ToRdTask());
}
///
/// Wrapper method to simplify search with overload resolution for two methods in RdEndpoint.
///
/// Used for sync calls only.
///
public static void SetHandler(RdCall endpoint, Func> handler)
{
var scheduler = new SwitchingScheduler(endpoint);
endpoint.SetRdTask(handler, scheduler, scheduler);
}
private object? ActivateMember(Type memberType, string memberName)
{
var typeInfo = memberType.GetTypeInfo();
var implementingType = ReflectionSerializerVerifier.GetImplementingType(typeInfo);
object? result;
if (implementingType.GetTypeInfo().IsGenericType)
{
result = ActivateGenericMember(memberName, typeInfo);
}
else if (!ReflectionSerializerVerifier.IsScalar(memberType))
{
result = ActivateRd(memberType);
}
else
{
result = null;
}
SetAsync(result);
return result;
}
private static void SetAsync(object? result)
{
if (result is IRdReactive activatedBindable)
{
//foreach (var _ in mi.GetCustomAttributes(typeof(RdAsyncAttribute), false))
activatedBindable.Async = true;
}
}
private SerializerPair GetProperSerializer(Type type)
{
// registration for all statically known types
myTypesCatalog?.AddType(type);
return mySerializers.GetOrRegisterSerializerPair(type, true);
}
private object? ActivateGenericMember(string memberName, TypeInfo memberType)
{
var implementingType = ReflectionSerializerVerifier.GetImplementingType(memberType);
var genericDefinition = implementingType.GetGenericTypeDefinition();
var genericArguments = implementingType.GetTypeInfo().GetGenericArguments();
var argument = genericArguments[0];
var serializerPair = GetProperSerializer(argument);
if (genericDefinition == typeof(RdProperty<>) ||
genericDefinition == typeof(RdSignal<>) ||
genericDefinition == typeof(RdSet<>))
{
var instance = Activator.CreateInstance(implementingType, serializerPair.Reader, serializerPair.Writer);
if (IsMonomorphic(argument))
ReflectionUtil.InvokeStaticGeneric(typeof(ReflectionRdActivator), nameof(SetOptimizeNested1), argument, instance);
return instance;
}
if (genericDefinition == typeof(RdList<>))
{
var instance = Activator.CreateInstance(implementingType, serializerPair.Reader, serializerPair.Writer, 1L /*nextVersion*/);
if (IsMonomorphic(argument))
ReflectionUtil.InvokeStaticGeneric(typeof(ReflectionRdActivator), nameof(SetOptimizeNested1), argument, instance);
return instance;
}
if (genericArguments.Length == 2)
{
var argument2 = genericArguments[1];
var serializerPair2 = GetProperSerializer(argument2);
var instance = Activator.CreateInstance(implementingType, serializerPair.Reader, serializerPair.Writer, serializerPair2.Reader, serializerPair2.Writer);
if (IsMonomorphic(argument2))
ReflectionUtil.InvokeStaticGeneric2(typeof(ReflectionRdActivator), nameof(SetOptimizeNested2), argument, argument2, instance);
return instance;
}
if (genericDefinition == typeof(Nullable<>))
{
// already initialized to null
return null;
}
// hack for UProperty & USignal
if (genericArguments.Length == 1 && typeof(IRdBindable).IsAssignableFrom(implementingType) && implementingType.IsClass)
{
foreach (var ctor in implementingType.GetTypeInfo().GetConstructors(BindingFlags.Public | BindingFlags.Instance))
{
var parameters = ctor.GetParameters();
if (parameters.Length == 3 && parameters[0].Name == "id")
{
return Activator.CreateInstance(implementingType, memberName, serializerPair.Reader, serializerPair.Writer);
}
}
}
throw new Assertion.AssertionException($"Unable to activate generic type: {memberType}");
static bool IsMonomorphic(Type type)
{
return type.IsValueType || (!typeof(IRdBindable).IsAssignableFrom(type) && type.IsSealed);
}
}
[UsedImplicitly]
private static void SetOptimizeNested1(object container) where T : notnull
{
if (container is RdProperty property)
{
property.OptimizeNested = true;
}
else if (container is RdSet set)
{
set.OptimizeNested = true;
}
else if (container is RdList list)
{
list.OptimizeNested = true;
}
}
[UsedImplicitly]
private static void SetOptimizeNested2(object container) where TKey : notnull
{
if (container is RdMap map)
map.OptimizeNested = true;
}
public static string GetTypeName(Type type)
{
var typename = type.AssemblyQualifiedName;
if (typeof(RdExtReflectionBindableBase).IsAssignableFrom(type))
{
var rpcInterface = ReflectionSerializerVerifier.GetRpcInterface(type.GetTypeInfo());
if (rpcInterface != null)
return rpcInterface.AssemblyQualifiedName;
}
return typename;
}
}
}