JD2022-TU1/main/tools/framework/JD.Reflection/MefImplementations/Metadata/MetadataViewGenerator.cs

347 lines
No EOL
16 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using JD.Reflection.MefImplementations.Hosting.Caching.Internal;
namespace JD.Reflection.MefImplementations.Metadata
{
// // Assume TMetadataView is
// // interface Foo
// // {
// // public typeRecord1 Record1 { get; }
// // public typeRecord2 Record2 { get; }
// // public typeRecord3 Record3 { get; }
// // public typeRecord4 Record4 { get; }
// // }
// //
// // The class to be generated will look approximately like:
// public class __Foo__MedataViewProxy : TMetadataView
// {
// public __Foo__MedataViewProxy(IReadOnlyDictionary<string, object> metadata)
// {
// Throw.IfNull(metadata, "metadata");
//
// try
// {
// Record1 = (typeRecord1)Record1;
// Record2 = (typeRecord2)Record2;
// Record3 = (typeRecord3)Record3;
// Record4 = (typeRecord4)Record4;
// }
// catch(InvalidCastException ice)
// {
// //Annotate exception .Data with diagnostic info
// }
// catch(NullReferenceException ice)
// {
// //Annotate exception .Data with diagnostic info
// }
// }
//
// // Interface
// public typeRecord1 Record1 { get; }
// public typeRecord2 Record2 { get; }
// public typeRecord3 Record3 { get; }
// public typeRecord4 Record4 { get; }
// }
internal static class MetadataViewGenerator
{
#region Constants
public const string MetadataViewType = "MetadataViewType";
public const string MetadataItemKey = "MetadataItemKey";
public const string MetadataItemTargetType = "MetadataItemTargetType";
public const string MetadataItemSourceType = "MetadataItemSourceType";
public const string MetadataItemValue = "MetadataItemValue";
#endregion
#region Variables
private static readonly object ms_lock = new object();
private static readonly Dictionary<Type, Type> ms_cache = new Dictionary<Type, Type>();
private static readonly AssemblyName ms_proxyAssemblyName = new AssemblyName(string.Format(CultureInfo.InvariantCulture, "MetadataViewProxies_{0}", Guid.NewGuid()));
private static readonly AssemblyBuilder ms_proxyAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(ms_proxyAssemblyName, AssemblyBuilderAccess.Run);
private static readonly ModuleBuilder ms_proxyModuleBuilder = ms_proxyAssemblyBuilder.DefineDynamicModule("MetadataViewProxiesModule");
private static readonly GenerationServices ms_generationServices = new GenerationServices(ms_proxyModuleBuilder);
private static readonly Type[] ms_ctorArgumentTypes = { typeof(IReadOnlyDictionary<string, object>) };
private static readonly ConstructorInfo ms_objectCtor = typeof(object).GetConstructor(Type.EmptyTypes);
private static readonly MethodInfo ms_tryGetValueMethodInfo = ms_ctorArgumentTypes[0].GetMethod("TryGetValue");
private static readonly MethodInfo ms_exceptionGetData = typeof(Exception).GetProperty("Data").GetGetMethod();
private static readonly MethodInfo ms_dictionaryAdd = typeof(IDictionary).GetMethod("Add");
private static readonly MethodInfo ms_objectGetType = typeof(object).GetMethod("GetType", Type.EmptyTypes);
#endregion
#region Methods
public static Type GenerateView(Type metadataViewType)
{
Debug.Assert(metadataViewType != null && metadataViewType.IsInterface);
lock (ms_lock)
{
Type proxyType;
if (ms_cache.TryGetValue(metadataViewType, out proxyType))
{
return proxyType;
}
if (!ms_cache.TryGetValue(metadataViewType, out proxyType))
{
proxyType = GenerateViewImpl(metadataViewType);
ms_cache.Add(metadataViewType, proxyType);
}
Debug.Assert(proxyType != null);
return proxyType;
}
}
#region Generation
private static Type GenerateViewImpl(Type metadataViewType)
{
TypeBuilder proxyTypeBuilder = ms_proxyModuleBuilder.DefineType(
string.Format(CultureInfo.InvariantCulture, "_proxy_{0}_{1}", metadataViewType.FullName, Guid.NewGuid()),
TypeAttributes.Public,
typeof (object),
new[] { metadataViewType });
// Implement Constructor
ILGenerator proxyCtorIl = CreateGeneratorForPublicConstructor(proxyTypeBuilder, ms_ctorArgumentTypes);
LocalBuilder exception = proxyCtorIl.DeclareLocal(typeof(Exception));
LocalBuilder exceptionData = proxyCtorIl.DeclareLocal(typeof(IDictionary));
LocalBuilder sourceType = proxyCtorIl.DeclareLocal(typeof(Type));
LocalBuilder value = proxyCtorIl.DeclareLocal(typeof(object));
LocalBuilder usesExportedMD = proxyCtorIl.DeclareLocal(typeof(bool));
Label tryConstructView = proxyCtorIl.BeginExceptionBlock();
// Implement interface properties
foreach (PropertyInfo propertyInfo in GetAllProperties(metadataViewType))
{
string fieldName = string.Format(CultureInfo.InvariantCulture, "_{0}_{1}", propertyInfo.Name, Guid.NewGuid());
string propertyName = string.Format(CultureInfo.InvariantCulture, "{0}", propertyInfo.Name);
if (propertyInfo.CanWrite)
{
throw new NotSupportedException(
$"The MetadataView '{metadataViewType.FullName}' is invalid because property '{propertyName}' has a property set method.");
}
if (!propertyInfo.CanRead)
{
continue;
}
Type[] propertyTypeArguments = new [] { propertyInfo.PropertyType };
Type[] optionalModifiers = propertyInfo.GetOptionalCustomModifiers();
Type[] requiredModifiers = propertyInfo.GetRequiredCustomModifiers();
Array.Reverse(optionalModifiers);
Array.Reverse(requiredModifiers);
// Generate field
FieldBuilder proxyFieldBuilder = proxyTypeBuilder.DefineField(fieldName, propertyInfo.PropertyType, FieldAttributes.Private);
// Generate property
PropertyBuilder proxyPropertyBuilder = proxyTypeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyInfo.PropertyType, propertyTypeArguments);
// Generate constructor code for retrieving the metadata value and setting the field
Label tryCastValue = proxyCtorIl.BeginExceptionBlock();
var defaultValueAttribute = propertyInfo.GetAttribute<DefaultValueAttribute>(false);
if (defaultValueAttribute != null)
{
proxyCtorIl.BeginExceptionBlock();
}
// In constructor set the backing field with the value from the dictionary
Label doneGettingDefaultValue = proxyCtorIl.DefineLabel();
GenerateLocalAssignmentFromFlag(proxyCtorIl, usesExportedMD, true);
proxyCtorIl.Emit(OpCodes.Ldarg_1);
proxyCtorIl.Emit(OpCodes.Ldstr, propertyInfo.Name);
proxyCtorIl.Emit(OpCodes.Ldloca, value);
proxyCtorIl.Emit(OpCodes.Callvirt, ms_tryGetValueMethodInfo);
proxyCtorIl.Emit(OpCodes.Brtrue, doneGettingDefaultValue);
proxyCtorIl.GenerateLocalAssignmentFromFlag(usesExportedMD, false);
if (defaultValueAttribute != null)
{
proxyCtorIl.GenerateLocalAssignmentFromDefaultAttribute(defaultValueAttribute, value);
}
proxyCtorIl.MarkLabel(doneGettingDefaultValue);
proxyCtorIl.GenerateFieldAssignmentFromLocalValue(value, proxyFieldBuilder);
proxyCtorIl.Emit(OpCodes.Leave, tryCastValue);
if (defaultValueAttribute != null)
{
proxyCtorIl.BeginCatchBlock(typeof(InvalidCastException));
{
Label notUsesExportedMd = proxyCtorIl.DefineLabel();
proxyCtorIl.Emit(OpCodes.Ldloc, usesExportedMD);
proxyCtorIl.Emit(OpCodes.Brtrue, notUsesExportedMd);
proxyCtorIl.Emit(OpCodes.Rethrow);
proxyCtorIl.MarkLabel(notUsesExportedMd);
proxyCtorIl.GenerateLocalAssignmentFromDefaultAttribute(defaultValueAttribute, value);
proxyCtorIl.GenerateFieldAssignmentFromLocalValue(value, proxyFieldBuilder);
}
proxyCtorIl.EndExceptionBlock();
}
// catch blocks for tryCast start here
proxyCtorIl.BeginCatchBlock(typeof(NullReferenceException));
{
proxyCtorIl.Emit(OpCodes.Stloc, exception);
proxyCtorIl.GetExceptionDataAndStoreInLocal(exception, exceptionData);
proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataItemKey, propertyName);
proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataItemTargetType, propertyInfo.PropertyType);
proxyCtorIl.Emit(OpCodes.Rethrow);
}
proxyCtorIl.BeginCatchBlock(typeof(InvalidCastException));
{
proxyCtorIl.Emit(OpCodes.Stloc, exception);
proxyCtorIl.GetExceptionDataAndStoreInLocal(exception, exceptionData);
proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataItemKey, propertyName);
proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataItemTargetType, propertyInfo.PropertyType);
proxyCtorIl.Emit(OpCodes.Rethrow);
}
proxyCtorIl.EndExceptionBlock();
// Generate "get" method implementation.
MethodBuilder getMethodBuilder = proxyTypeBuilder.DefineMethod(
string.Format(CultureInfo.InvariantCulture, "get_{0}", propertyName),
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final,
CallingConventions.HasThis,
propertyInfo.PropertyType,
requiredModifiers,
optionalModifiers,
Type.EmptyTypes, null, null);
proxyTypeBuilder.DefineMethodOverride(getMethodBuilder, propertyInfo.GetGetMethod());
ILGenerator getMethodIL = getMethodBuilder.GetILGenerator();
getMethodIL.Emit(OpCodes.Ldarg_0);
getMethodIL.Emit(OpCodes.Ldfld, proxyFieldBuilder);
getMethodIL.Emit(OpCodes.Ret);
proxyPropertyBuilder.SetGetMethod(getMethodBuilder);
}
proxyCtorIl.Emit(OpCodes.Leave, tryConstructView);
// catch blocks for constructView start here
proxyCtorIl.BeginCatchBlock(typeof(NullReferenceException));
{
proxyCtorIl.Emit(OpCodes.Stloc, exception);
proxyCtorIl.GetExceptionDataAndStoreInLocal(exception, exceptionData);
proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataViewType, metadataViewType);
proxyCtorIl.Emit(OpCodes.Rethrow);
}
proxyCtorIl.BeginCatchBlock(typeof(InvalidCastException));
{
proxyCtorIl.Emit(OpCodes.Stloc, exception);
proxyCtorIl.GetExceptionDataAndStoreInLocal(exception, exceptionData);
proxyCtorIl.Emit(OpCodes.Ldloc, value);
proxyCtorIl.Emit(OpCodes.Call, ms_objectGetType);
proxyCtorIl.Emit(OpCodes.Stloc, sourceType);
proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataViewType, metadataViewType);
proxyCtorIl.AddLocalToLocalDictionary(exceptionData, MetadataItemSourceType, sourceType);
proxyCtorIl.AddLocalToLocalDictionary(exceptionData, MetadataItemValue, value);
proxyCtorIl.Emit(OpCodes.Rethrow);
}
proxyCtorIl.EndExceptionBlock();
// Finished implementing interface and constructor
proxyCtorIl.Emit(OpCodes.Ret);
return proxyTypeBuilder.CreateType();
}
private static IEnumerable<PropertyInfo> GetAllProperties(Type type)
{
return type.GetInterfaces().Concat(new [] { type }).SelectMany(iface => iface.GetProperties());
}
private static ILGenerator CreateGeneratorForPublicConstructor(TypeBuilder typeBuilder, Type[] ctrArgumentTypes)
{
ConstructorBuilder ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ctrArgumentTypes);
ILGenerator ctorIL = ctorBuilder.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, ms_objectCtor);
return ctorIL;
}
private static void GenerateLocalAssignmentFromFlag(this ILGenerator il, LocalBuilder local, bool flag)
{
il.Emit(flag ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc, local);
}
private static void GenerateLocalAssignmentFromDefaultAttribute(this ILGenerator il, DefaultValueAttribute attribute, LocalBuilder local)
{
Debug.Assert(attribute != null);
ms_generationServices.LoadValue(il, attribute.Value);
if (attribute.Value != null && attribute.Value.GetType().IsValueType)
{
il.Emit(OpCodes.Box, attribute.Value.GetType());
}
il.Emit(OpCodes.Stloc, local);
}
private static void GenerateFieldAssignmentFromLocalValue(this ILGenerator il, LocalBuilder local, FieldInfo field)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldloc, local);
il.Emit(field.FieldType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, field.FieldType);
il.Emit(OpCodes.Stfld, field);
}
private static void GetExceptionDataAndStoreInLocal(this ILGenerator ilGenerator, LocalBuilder exception, LocalBuilder dataStore)
{
ilGenerator.Emit(OpCodes.Ldloc, exception);
ilGenerator.Emit(OpCodes.Callvirt, ms_exceptionGetData);
ilGenerator.Emit(OpCodes.Stloc, dataStore);
}
private static void AddItemToLocalDictionary(this ILGenerator ilGenerator, LocalBuilder dictionary, object key, object value)
{
ilGenerator.Emit(OpCodes.Ldloc, dictionary);
ms_generationServices.LoadValue(ilGenerator, key);
ms_generationServices.LoadValue(ilGenerator, value);
ilGenerator.Emit(OpCodes.Callvirt, ms_dictionaryAdd);
}
private static void AddLocalToLocalDictionary(this ILGenerator ilGenerator, LocalBuilder dictionary, object key, LocalBuilder value)
{
ilGenerator.Emit(OpCodes.Ldloc, dictionary);
ms_generationServices.LoadValue(ilGenerator, key);
ilGenerator.Emit(OpCodes.Ldloc, value);
ilGenerator.Emit(OpCodes.Callvirt, ms_dictionaryAdd);
}
#endregion
#endregion
}
}