using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace Core.Common.Utils.Aop
{
///
/// This class takes care of manually initializing uninitialized entity aspects. Each level of the instance
/// (type and all base types) may have their own aspects. We need to do this explicitly because sometimes,
/// due to virtual calls in (base) constructors, aspects are required before the (derived) constructor of a
/// class has ran. In the constructor PostSharp initializes the aspect for each class.
///
/// There is still an open issue: any fields touched before the (derived) constructor executes are
/// subscribed to the wrong entity attribute. This can happen if you have virtual properties that don't call
/// base but use their own field.
/// It may not look this way, but this code is already pretty optimized. If you see room for
/// improvement, please verify it using the tests in EntityAttributeTest
internal static class ManualAspectInitializer
{
private delegate T FieldGetterDelegate(object obj);
private static readonly object LastTypeLock = new object();
//'first level cache'
private static Type lastType;
private static AspectInfo lastAspectInfo;
//'second level cache'
private static readonly IDictionary AspectLookup = new Dictionary();
public static void InitializeAspects(object instance)
{
InitializeAspectsManually(instance, GetAspectInfo(instance.GetType()));
}
private static void InitializeAspectsManually(object instance, AspectInfo info)
{
while (info != null)
{
var baseInfo = info.BaseAspectInfo;
if (baseInfo == null)
{
//current info is of the lowest baseclass (there is no baseinfo), skip because we know this
//must already have been initialized (since that triggered us to come here)
return;
}
if (!info.IsEmpty)
{
var aspect = info.AspectGetter(instance);
if (aspect != null)
{
return; //aspect already initialized, don't do it again!
}
//actual initialize
info.InitializeMethod.Invoke(instance, new object[0]);
}
info = baseInfo;
}
}
private static AspectInfo GetAspectInfo(Type type)
{
//first check the first level cache: we can expect subsequent calls for the same type due to the following reasons:
//1. Creating one instance calls this method for each type in the hierarchy (always with the toplevel type)
//2. We usually construct multiple objects of the same type in a row
lock (LastTypeLock)
{
if (lastType != type)
{
lastType = type;
lastAspectInfo = LookupAspectInfoForHierarchy(type);
}
return lastAspectInfo;
}
}
private static AspectInfo LookupAspectInfoForHierarchy(Type typeLevel)
{
bool createdNew;
var info = LookupAspectInfoForType(typeLevel, out createdNew);
if (createdNew)
{
var baseType = typeLevel.BaseType;
if (baseType != null)
{
info.BaseAspectInfo = LookupAspectInfoForHierarchy(baseType);
}
}
return info;
}
private static AspectInfo LookupAspectInfoForType(Type typeLevel, out bool created)
{
AspectInfo aspectInfo;
created = false;
if (!AspectLookup.TryGetValue(typeLevel, out aspectInfo))
{
var aspectField = typeLevel.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
.SingleOrDefault(f => f.FieldType == typeof(EntityAttribute));
if (aspectField != null)
{
var initializeMethod = typeLevel.GetMethod("<>z__InitializeAspects",
BindingFlags.Instance | BindingFlags.NonPublic);
aspectInfo = new AspectInfo(FieldGetterUsingIl(typeLevel, aspectField),
initializeMethod);
}
else
{
aspectInfo = new AspectInfo();
}
AspectLookup.Add(typeLevel, aspectInfo);
created = true;
}
return aspectInfo;
}
private static FieldGetterDelegate FieldGetterUsingIl(Type objectType, FieldInfo fieldInfo)
{
var dm = new DynamicMethod("GetAspect", typeof(TValue), new[]
{
typeof(object)
}, objectType, true);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, fieldInfo);
il.Emit(OpCodes.Ret);
return (FieldGetterDelegate) dm.CreateDelegate(typeof(FieldGetterDelegate));
}
private class AspectInfo
{
public readonly FieldGetterDelegate AspectGetter;
public readonly MethodInfo InitializeMethod;
public readonly bool IsEmpty = false;
public AspectInfo BaseAspectInfo;
public AspectInfo()
{
IsEmpty = true;
}
public AspectInfo(FieldGetterDelegate aspectGetter, MethodInfo initializeMethod)
{
AspectGetter = aspectGetter;
InitializeMethod = initializeMethod;
}
}
}
}