react-native-windows/vnext/Microsoft.ReactNative.Managed/JSValueWriterGenerator.cs

289 строки
12 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml.Schema;
using static Microsoft.ReactNative.Managed.JSValueGenerator;
using static System.Linq.Expressions.Expression;
namespace Microsoft.ReactNative.Managed
{
public static class JSValueWriterGenerator
{
// The current assembly to ensure we always register the basic type readers
private static readonly Assembly s_currentAssembly = typeof(JSValueReaderGenerator).Assembly;
private static readonly ConcurrentDictionary<Assembly, bool> s_registeredAssemblies = new ConcurrentDictionary<Assembly, bool>();
private static readonly ConcurrentDictionary<Type, MethodInfo> s_codeGenerateGenericExtensionMethods = new ConcurrentDictionary<Type, MethodInfo>();
private static readonly Lazy<KeyValuePair<Type, MethodInfo>[]> s_allMethods;
private static readonly Lazy<IReadOnlyDictionary<Type, MethodInfo>> s_nonGenericMethods;
private static readonly Lazy<IReadOnlyDictionary<Type, SortedList<Type, MethodInfo>>> s_genericMethods;
public static void RegisterAssembly(Assembly assembly)
{
// UnitTests re-register over and over, safe to skip if already added.
if (s_registeredAssemblies.ContainsKey(assembly))
{
return;
}
s_registeredAssemblies.GetOrAdd(assembly, true);
// Fail programs that register after we started serializing values.
if (s_allMethods.IsValueCreated)
{
throw new InvalidOperationException("Cannot register assemblies dynamically after the first value is serialized.");
}
}
public static void RegisterCodeGeneratorGenericExtensionMethod(Type type, MethodInfo method)
{
if (!method.IsGenericMethod)
{
throw new InvalidOperationException("Cannot register non generic methods.");
}
s_codeGenerateGenericExtensionMethods.TryAdd(type, method);
}
static JSValueWriterGenerator()
{
s_registeredAssemblies.GetOrAdd(s_currentAssembly, true);
// Get all extension WriteValue methods for IJSValueWriter.
// The first parameter must be IJSValueWriter.
// The second parameter must be not a generic parameter T.
// This is to avoid endless recursion because WriteValue with generic parameter T
// is the one who calls the JSValueWriterGenerator.
s_allMethods = new Lazy<KeyValuePair<Type, MethodInfo>[]>(() =>
{
var extensionMethods =
from assembly in s_registeredAssemblies.Keys
from type in assembly.GetTypes()
let typeInfo = type.GetTypeInfo()
where typeInfo.IsSealed && !typeInfo.IsGenericType && !typeInfo.IsNested
from member in type.GetMember(nameof(JSValueWriter.WriteValue), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
let method = member as MethodInfo
where (method != null) && method.IsDefined(typeof(ExtensionAttribute), inherit: false)
let parameters = method.GetParameters()
where parameters.Length == 2
&& parameters[0].ParameterType == typeof(IJSValueWriter)
let dataType = parameters[1].ParameterType
where !dataType.IsByRef && !dataType.IsGenericParameter
select new KeyValuePair<Type, MethodInfo>(dataType, method);
return extensionMethods.ToArray();
});
// Get all non-generic WriteValue extension methods.
// They are easy to match and we can put them in a dictionary.
s_nonGenericMethods = new Lazy<IReadOnlyDictionary<Type, MethodInfo>>(() =>
s_allMethods.Value.Where(p => !p.Value.IsGenericMethod).ToDictionary(p => p.Key, p => p.Value));
// Get all generic WriteValue extension methods.
// Group them by generic type definitions and sort them by having more specific methods getting first.
// The matching for generic types is more complicated: we first match the generic type definition,
// and then walk the sorted list and try to match types one by one.
// Note that the parameter could be an array of generic type. The array is not a generic type.
s_genericMethods = new Lazy<IReadOnlyDictionary<Type, SortedList<Type, MethodInfo>>>(() =>
{
var genericMethods =
from pair in s_allMethods.Value.Union(s_codeGenerateGenericExtensionMethods)
where pair.Value.IsGenericMethod
let type = pair.Key
let keyType = type.GetTypeInfo().IsGenericType ? type.GetGenericTypeDefinition()
: type.IsArray ? typeof(Array)
: throw new InvalidOperationException($"Unsupported argument type {type}")
group pair by keyType into g
select new KeyValuePair<Type, SortedList<Type, MethodInfo>>(
g.Key, new SortedList<Type, MethodInfo>(
g.ToDictionary(p => p.Key, p => p.Value), GenericTypeComparer.Default));
return genericMethods.ToDictionary(p => p.Key, p => p.Value);
});
}
public static MethodInfo WriteValueOf(Type typeArg)
{
var readValueNoParamMethod =
from member in typeof(JSValueWriter).GetMember(
nameof(JSValueWriter.WriteValue), BindingFlags.Static | BindingFlags.Public)
let method = member as MethodInfo
where method != null
&& method.IsGenericMethod
&& method.IsDefined(typeof(ExtensionAttribute), inherit: false)
let parameters = method.GetParameters()
where parameters.Length == 2
&& parameters[0].ParameterType == typeof(IJSValueWriter)
&& parameters[1].ParameterType.IsGenericParameter
select method;
return readValueNoParamMethod.First().MakeGenericMethod(typeArg);
}
public static MethodInfo WriteArgsOf(params Type[] typeArgs)
{
var writeArgsMethod =
from member in typeof(JSValueWriter).GetMember(
nameof(JSValueWriter.WriteArgs), BindingFlags.Static | BindingFlags.Public)
let method = member as MethodInfo
let isGeneric = method.IsGenericMethod
where method != null
&& method.IsDefined(typeof(ExtensionAttribute), inherit: false)
let parameters = method.GetParameters()
where parameters.Length == typeArgs.Length + 1
&& parameters[0].ParameterType == typeof(IJSValueWriter)
select isGeneric ? method.MakeGenericMethod(typeArgs) : method;
return writeArgsMethod.First();
}
private static MethodInfo MethodOf(string methodName, params Type[] typeArgs) =>
typeof(JSValueWriterGenerator).GetMethod(methodName).MakeGenericMethod(typeArgs);
static TypeWrapper WriteValueDelegateOf(Type typeArg) =>
new TypeWrapper(typeof(WriteValueDelegate<>).MakeGenericType(typeArg));
static MethodInfo WriteObjectPropertyOf(Type typeArg)
{
var writeObjectProperty =
from member in typeof(JSValueWriter).GetMember(
nameof(JSValueWriter.WriteObjectProperty), BindingFlags.Static | BindingFlags.Public)
let method = member as MethodInfo
where method != null
&& method.IsGenericMethod
&& method.IsDefined(typeof(ExtensionAttribute), inherit: false)
let parameters = method.GetParameters()
where parameters.Length == 3
&& parameters[0].ParameterType == typeof(IJSValueWriter)
&& parameters[1].ParameterType == typeof(string)
select method;
return writeObjectProperty.First().MakeGenericMethod(typeArg);
}
static MethodInfo WriteObjectBegin => typeof(IJSValueWriter).GetMethod(nameof(IJSValueWriter.WriteObjectBegin));
static MethodInfo WriteObjectEnd => typeof(IJSValueWriter).GetMethod(nameof(IJSValueWriter.WriteObjectEnd));
static MethodInfo WriteNull => typeof(IJSValueWriter).GetMethod(nameof(IJSValueWriter.WriteNull));
public static Delegate GenerateWriteValueDelegate(Type valueType)
{
return GenerateWriteValueFromExtension(valueType)
?? GenerateWriteValueFromGenericExtension(valueType)
?? GenerateWriteValueForEnum(valueType)
?? GenerateWriteValueForClass(valueType)
?? throw new Exception($"Cannot generate WriteValue delegate for type {valueType}");
}
// Creates a delegate from the WriteValue non-generic extension method
private static Delegate GenerateWriteValueFromExtension(Type valueType)
{
if (s_nonGenericMethods.Value.TryGetValue(valueType, out MethodInfo methodInfo))
{
return methodInfo.CreateDelegate(typeof(WriteValueDelegate<>).MakeGenericType(valueType));
}
return null;
}
private static Delegate GenerateWriteValueFromGenericExtension(Type valueType)
{
var keyType = valueType.GetTypeInfo().IsGenericType ? valueType.GetGenericTypeDefinition()
: valueType.IsArray ? typeof(Array)
: null;
if (keyType != null
&& s_genericMethods.Value.TryGetValue(keyType, out SortedList<Type, MethodInfo> candidateMethods))
{
foreach (var candidateMethod in candidateMethods)
{
var genericType = candidateMethod.Key;
var methodInfo = candidateMethod.Value;
var genericArgs = methodInfo.GetGenericArguments();
if (TryMatchGenericType(valueType, genericType, genericArgs, out Type[] typeArgs))
{
var genericMethod = methodInfo.MakeGenericMethod(typeArgs);
return genericMethod.CreateDelegate(typeof(WriteValueDelegate<>).MakeGenericType(valueType));
}
}
}
return null;
}
// It cannot be an extension method because it would conflict with the generic
// extension method that uses T value type.
public static void WriteEnum<TEnum>(IJSValueWriter writer, TEnum value) where TEnum : Enum
{
writer.WriteValue((int)(object)value);
}
private static Delegate GenerateWriteValueForEnum(Type valueType)
{
// Creates a delegate from the WriteEnum method
return valueType.GetTypeInfo().IsEnum
? MethodOf(nameof(WriteEnum), valueType).CreateDelegate(typeof(WriteValueDelegate<>).MakeGenericType(valueType))
: null;
}
private static Delegate GenerateWriteValueForClass(Type valueType)
{
// Generate code that looks like:
//
// (IJSValueWriter writer, Type value) =>
// {
// if (value != null) // we do not check it for structs
// {
// writer.WriteObjectBegin();
// writer.WriteObjectProperty("Field1", value.Field1);
// writer.WriteObjectProperty("Field2", value.Field2);
// writer.WriteObjectProperty("Prop1", value.Prop1);
// writer.WriteObjectProperty("Prop2", value.Prop2);
// writer.WriteObjectEnd();
// }
// else
// {
// writer.WriteNull();
// }
// }
var valueTypeInfo = valueType.GetTypeInfo();
bool isStruct = valueTypeInfo.IsValueType && !valueTypeInfo.IsEnum;
bool isClass = valueTypeInfo.IsClass;
if (isStruct || isClass)
{
var fields =
from field in valueType.GetFields(BindingFlags.Public | BindingFlags.Instance)
where !field.IsInitOnly
select new { field.Name, Type = field.FieldType };
var properties =
from property in valueType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
let propertySetter = property.SetMethod
where propertySetter != null && propertySetter.IsPublic
select new { property.Name, Type = property.PropertyType };
var members = fields.Concat(properties).ToArray();
return WriteValueDelegateOf(valueType).CompileLambda(
Parameter(typeof(IJSValueWriter), out var writer),
Parameter(valueType, out var value),
isStruct
? Block(
writer.Call(WriteObjectBegin),
Block(members.Select(member =>
writer.CallExt(WriteObjectPropertyOf(member.Type), Constant(member.Name), value.Property(member.Name)))),
writer.Call(WriteObjectEnd)) as Expression
: IfThenElse(NotEqual(value, Constant(null)),
ifTrue: Block(
writer.Call(WriteObjectBegin),
Block(members.Select(member =>
writer.CallExt(WriteObjectPropertyOf(member.Type), Constant(member.Name), value.Property(member.Name)))),
writer.Call(WriteObjectEnd)),
ifFalse: writer.Call(WriteNull)));
}
return null;
}
}
}