tools/AutoMapper/Execution/TypeMapPlanBuilder.cs (502 lines of code) (raw):
namespace AutoMapper.Execution
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Configuration;
using static System.Linq.Expressions.Expression;
using static Internal.ExpressionFactory;
using static ExpressionBuilder;
using System.Diagnostics;
public class TypeMapPlanBuilder
{
private static readonly Expression<Func<AutoMapperMappingException>> CtorExpression =
() => new AutoMapperMappingException(null, null, default(TypePair), null, null);
private static readonly Expression<Action<ResolutionContext>> IncTypeDepthInfo =
ctxt => ctxt.IncrementTypeDepth(default(TypePair));
private static readonly Expression<Action<ResolutionContext>> ValidateMap =
ctxt => ctxt.ValidateMap(default(TypeMap));
private static readonly Expression<Action<ResolutionContext>> DecTypeDepthInfo =
ctxt => ctxt.DecrementTypeDepth(default(TypePair));
private static readonly Expression<Func<ResolutionContext, int>> GetTypeDepthInfo =
ctxt => ctxt.GetTypeDepth(default(TypePair));
private readonly IConfigurationProvider _configurationProvider;
private readonly ParameterExpression _destination;
private readonly ParameterExpression _initialDestination;
private readonly TypeMap _typeMap;
public TypeMapPlanBuilder(IConfigurationProvider configurationProvider, TypeMap typeMap)
{
_configurationProvider = configurationProvider;
_typeMap = typeMap;
Source = Parameter(typeMap.SourceType, "src");
_initialDestination = Parameter(typeMap.DestinationTypeToUse, "dest");
Context = Parameter(typeof(ResolutionContext), "ctxt");
_destination = Variable(_initialDestination.Type, "typeMapDestination");
}
public ParameterExpression Source { get; }
public ParameterExpression Context { get; }
public LambdaExpression CreateMapperLambda(Stack<TypeMap> typeMapsPath)
{
if (_typeMap.SourceType.IsGenericTypeDefinition() ||
_typeMap.DestinationTypeToUse.IsGenericTypeDefinition())
return null;
var customExpression = TypeConverterMapper() ??
_typeMap.Substitution ?? _typeMap.CustomMapper ?? _typeMap.CustomProjection;
if (customExpression != null)
return Lambda(customExpression.ReplaceParameters(Source, _initialDestination, Context), Source,
_initialDestination, Context);
CheckForCycles(typeMapsPath);
var destinationFunc = CreateDestinationFunc(out bool constructorMapping);
var assignmentFunc = CreateAssignmentFunc(destinationFunc, constructorMapping);
var mapperFunc = CreateMapperFunc(assignmentFunc);
var checkContext = CheckContext(_typeMap, Context);
var lambaBody = checkContext != null ? new[] {checkContext, mapperFunc} : new[] {mapperFunc};
return Lambda(Block(new[] {_destination}, lambaBody), Source, _initialDestination, Context);
}
private void CheckForCycles(Stack<TypeMap> typeMapsPath)
{
if(_typeMap.PreserveReferences)
{
return;
}
if(typeMapsPath == null)
{
typeMapsPath = new Stack<TypeMap>();
}
typeMapsPath.Push(_typeMap);
var properties =
from pm in _typeMap.GetPropertyMaps() where pm.CanResolveValue()
let propertyTypeMap = ResolvePropertyTypeMap(pm)
where propertyTypeMap != null && !propertyTypeMap.PreserveReferences
select new { PropertyTypeMap = propertyTypeMap, PropertyMap = pm };
foreach(var property in properties)
{
if(typeMapsPath.Count % _configurationProvider.MaxExecutionPlanDepth == 0)
{
property.PropertyMap.Inline = false;
Debug.WriteLine($"Resetting Inline: {property.PropertyMap.DestinationProperty} in {_typeMap.SourceType} - {_typeMap.DestinationType}");
}
var propertyTypeMap = property.PropertyTypeMap;
if(typeMapsPath.Contains(propertyTypeMap) && !propertyTypeMap.SourceType.IsValueType())
{
SetPreserveReferences(propertyTypeMap);
foreach(var derivedTypeMap in propertyTypeMap.IncludedDerivedTypes.Select(ResolveTypeMap))
{
SetPreserveReferences(derivedTypeMap);
}
}
else
{
propertyTypeMap.Seal(_configurationProvider, typeMapsPath);
}
}
typeMapsPath.Pop();
}
private void SetPreserveReferences(TypeMap propertyTypeMap)
{
Debug.WriteLine($"Setting PreserveReferences: {_typeMap.SourceType} - {_typeMap.DestinationType} => {propertyTypeMap.SourceType} - {propertyTypeMap.DestinationType}");
propertyTypeMap.PreserveReferences = true;
}
private TypeMap ResolvePropertyTypeMap(PropertyMap propertyMap)
{
if(propertyMap.SourceType == null)
{
return null;
}
var types = new TypePair(propertyMap.SourceType, propertyMap.DestinationPropertyType);
return ResolveTypeMap(types);
}
private TypeMap ResolveTypeMap(TypePair types)
{
var typeMap = _configurationProvider.ResolveTypeMap(types);
if(typeMap == null && _configurationProvider.FindMapper(types) is IObjectMapperInfo mapper)
{
typeMap = _configurationProvider.ResolveTypeMap(mapper.GetAssociatedTypes(types));
}
return typeMap;
}
private LambdaExpression TypeConverterMapper()
{
if (_typeMap.TypeConverterType == null)
return null;
Type type;
if (_typeMap.TypeConverterType.IsGenericTypeDefinition())
{
var genericTypeParam = _typeMap.SourceType.IsGenericType()
? _typeMap.SourceType.GetTypeInfo().GenericTypeArguments[0]
: _typeMap.DestinationTypeToUse.GetTypeInfo().GenericTypeArguments[0];
type = _typeMap.TypeConverterType.MakeGenericType(genericTypeParam);
}
else
{
type = _typeMap.TypeConverterType;
}
// (src, dest, ctxt) => ((ITypeConverter<TSource, TDest>)ctxt.Options.CreateInstance<TypeConverterType>()).ToType(src, ctxt);
var converterInterfaceType =
typeof(ITypeConverter<,>).MakeGenericType(_typeMap.SourceType, _typeMap.DestinationTypeToUse);
return Lambda(
Call(
ToType(CreateInstance(type), converterInterfaceType),
converterInterfaceType.GetDeclaredMethod("Convert"),
Source, _initialDestination, Context
),
Source, _initialDestination, Context);
}
private Expression CreateDestinationFunc(out bool constructorMapping)
{
var newDestFunc = ToType(CreateNewDestinationFunc(out constructorMapping), _typeMap.DestinationTypeToUse);
var getDest = _typeMap.DestinationTypeToUse.IsValueType()
? newDestFunc
: Coalesce(_initialDestination, newDestFunc);
Expression destinationFunc = Assign(_destination, getDest);
if (_typeMap.PreserveReferences)
{
var dest = Variable(typeof(object), "dest");
var setValue = Context.Type.GetDeclaredMethod("CacheDestination");
var set = Call(Context, setValue, Source, Constant(_destination.Type), _destination);
var setCache = IfThen(NotEqual(Source, Constant(null)), set);
destinationFunc = Block(new[] {dest}, Assign(dest, destinationFunc), setCache, dest);
}
return destinationFunc;
}
private Expression CreateAssignmentFunc(Expression destinationFunc, bool constructorMapping)
{
var actions = new List<Expression>();
foreach (var propertyMap in _typeMap.GetPropertyMaps().Where(pm => pm.CanResolveValue()))
{
var property = TryPropertyMap(propertyMap);
if (constructorMapping && _typeMap.ConstructorParameterMatches(propertyMap.DestinationProperty.Name))
property = _initialDestination.IfNullElse(Empty(), property);
actions.Add(property);
}
foreach (var pathMap in _typeMap.PathMaps.Where(pm => !pm.Ignored))
actions.Add(HandlePath(pathMap));
foreach (var beforeMapAction in _typeMap.BeforeMapActions)
actions.Insert(0, beforeMapAction.ReplaceParameters(Source, _destination, Context));
actions.Insert(0, destinationFunc);
if (_typeMap.MaxDepth > 0)
{
actions.Insert(0,
Call(Context, ((MethodCallExpression) IncTypeDepthInfo.Body).Method, Constant(_typeMap.Types)));
}
if (_typeMap.IsConventionMap && _typeMap.Profile.ValidateInlineMaps)
{
actions.Insert(0, Call(Context, ((MethodCallExpression)ValidateMap.Body).Method, Constant(_typeMap)));
}
actions.AddRange(
_typeMap.AfterMapActions.Select(
afterMapAction => afterMapAction.ReplaceParameters(Source, _destination, Context)));
if (_typeMap.MaxDepth > 0)
actions.Add(Call(Context, ((MethodCallExpression) DecTypeDepthInfo.Body).Method,
Constant(_typeMap.Types)));
actions.Add(_destination);
return Block(actions);
}
private Expression HandlePath(PathMap pathMap)
{
var destination = ((MemberExpression) pathMap.DestinationExpression.ConvertReplaceParameters(_destination))
.Expression;
var createInnerObjects = CreateInnerObjects(destination);
var setFinalValue = CreatePropertyMapFunc(new PropertyMap(pathMap), destination);
return Block(createInnerObjects, setFinalValue);
}
private Expression CreateInnerObjects(Expression destination) => Block(destination.GetMembers()
.Select(NullCheck)
.Reverse()
.Concat(new[] {Empty()}));
private Expression NullCheck(MemberExpression memberExpression)
{
var setter = GetSetter(memberExpression);
var ifNull = setter == null
? (Expression)
Throw(Constant(new NullReferenceException(
$"{memberExpression} cannot be null because it's used by ForPath.")))
: Assign(setter, DelegateFactory.GenerateConstructorExpression(memberExpression.Type));
return memberExpression.IfNullElse(ifNull);
}
private Expression CreateMapperFunc(Expression assignmentFunc)
{
var mapperFunc = assignmentFunc;
if(_typeMap.Condition != null)
mapperFunc =
Condition(_typeMap.Condition.Body,
mapperFunc, Default(_typeMap.DestinationTypeToUse));
if(_typeMap.MaxDepth > 0)
mapperFunc = Condition(
LessThanOrEqual(
Call(Context, ((MethodCallExpression)GetTypeDepthInfo.Body).Method, Constant(_typeMap.Types)),
Constant(_typeMap.MaxDepth)
),
mapperFunc,
Default(_typeMap.DestinationTypeToUse));
if(_typeMap.Profile.AllowNullDestinationValues)
mapperFunc = Source.IfNullElse(Default(_typeMap.DestinationTypeToUse), mapperFunc);
return CheckReferencesCache(mapperFunc);
}
private Expression CheckReferencesCache(Expression valueBuilder)
{
if(!_typeMap.PreserveReferences)
{
return valueBuilder;
}
var cache = Variable(_typeMap.DestinationTypeToUse, "cachedDestination");
var getDestination = Context.Type.GetDeclaredMethod("GetDestination");
var assignCache =
Assign(cache,
ToType(Call(Context, getDestination, Source, Constant(_destination.Type)), _destination.Type));
var condition = Condition(
AndAlso(NotEqual(Source, Constant(null)), NotEqual(assignCache, Constant(null))),
cache,
valueBuilder);
return Block(new[] { cache }, condition);
}
private Expression CreateNewDestinationFunc(out bool constructorMapping)
{
constructorMapping = false;
if (_typeMap.DestinationCtor != null)
return _typeMap.DestinationCtor.ReplaceParameters(Source, Context);
if (_typeMap.ConstructDestinationUsingServiceLocator)
return CreateInstance(_typeMap.DestinationTypeToUse);
if (_typeMap.ConstructorMap?.CanResolve == true)
{
constructorMapping = true;
return CreateNewDestinationExpression(_typeMap.ConstructorMap);
}
if (_typeMap.DestinationTypeToUse.IsInterface())
{
var ctor = Call(null,
typeof(DelegateFactory).GetDeclaredMethod(nameof(DelegateFactory.CreateCtor), new[] { typeof(Type) }),
Call(null,
typeof(ProxyGenerator).GetDeclaredMethod(nameof(ProxyGenerator.GetProxyType)),
Constant(_typeMap.DestinationTypeToUse)));
// We're invoking a delegate here to make it have the right accessibility
return Invoke(ctor);
}
return DelegateFactory.GenerateConstructorExpression(_typeMap.DestinationTypeToUse);
}
private Expression CreateNewDestinationExpression(ConstructorMap constructorMap)
{
var ctorArgs = constructorMap.CtorParams.Select(CreateConstructorParameterExpression);
var variables = constructorMap.Ctor.GetParameters().Select(parameter => Variable(parameter.ParameterType, parameter.Name)).ToArray();
var body = variables.Zip(ctorArgs,
(variable, expression) => (Expression) Assign(variable, ToType(expression, variable.Type)))
.Concat(new[] { CheckReferencesCache(New(constructorMap.Ctor, variables)) })
.ToArray();
return Block(variables, body);
}
private Expression CreateConstructorParameterExpression(ConstructorParameterMap ctorParamMap)
{
var valueResolverExpression = ResolveSource(ctorParamMap);
var sourceType = valueResolverExpression.Type;
var resolvedValue = Variable(sourceType, "resolvedValue");
return Block(new[] {resolvedValue},
Assign(resolvedValue, valueResolverExpression),
MapExpression(_configurationProvider, _typeMap.Profile, new TypePair(sourceType, ctorParamMap.DestinationType), resolvedValue, Context));
}
private Expression ResolveSource(ConstructorParameterMap ctorParamMap)
{
if (ctorParamMap.CustomExpression != null)
return ctorParamMap.CustomExpression.ConvertReplaceParameters(Source)
.NullCheck(ctorParamMap.DestinationType);
if (ctorParamMap.CustomValueResolver != null)
return ctorParamMap.CustomValueResolver.ConvertReplaceParameters(Source, Context);
if (ctorParamMap.Parameter.IsOptional)
{
ctorParamMap.DefaultValue = true;
return Constant(ctorParamMap.Parameter.GetDefaultValue(), ctorParamMap.Parameter.ParameterType);
}
return Chain(ctorParamMap.SourceMembers, ctorParamMap.DestinationType);
}
private Expression TryPropertyMap(PropertyMap propertyMap)
{
var pmExpression = CreatePropertyMapFunc(propertyMap, _destination);
if (pmExpression == null)
return null;
var exception = Parameter(typeof(Exception), "ex");
var mappingExceptionCtor = ((NewExpression) CtorExpression.Body).Constructor;
return TryCatch(Block(typeof(void), pmExpression),
MakeCatchBlock(typeof(Exception), exception,
Throw(New(mappingExceptionCtor, Constant("Error mapping types."), exception,
Constant(propertyMap.TypeMap.Types), Constant(propertyMap.TypeMap), Constant(propertyMap))),
null));
}
private Expression CreatePropertyMapFunc(PropertyMap propertyMap, Expression destination)
{
var destMember = MakeMemberAccess(destination, propertyMap.DestinationProperty);
Expression getter;
if (propertyMap.DestinationProperty is PropertyInfo pi && pi.GetGetMethod(true) == null)
getter = Default(propertyMap.DestinationPropertyType);
else
getter = destMember;
Expression destValueExpr;
if (propertyMap.UseDestinationValue)
{
destValueExpr = getter;
}
else
{
if (_initialDestination.Type.IsValueType())
destValueExpr = Default(propertyMap.DestinationPropertyType);
else
destValueExpr = Condition(Equal(_initialDestination, Constant(null)),
Default(propertyMap.DestinationPropertyType), getter);
}
var valueResolverExpr = BuildValueResolverFunc(propertyMap, getter);
var resolvedValue = Variable(valueResolverExpr.Type, "resolvedValue");
var setResolvedValue = Assign(resolvedValue, valueResolverExpr);
valueResolverExpr = resolvedValue;
var typePair = new TypePair(valueResolverExpr.Type, propertyMap.DestinationPropertyType);
valueResolverExpr = propertyMap.Inline
? MapExpression(_configurationProvider, _typeMap.Profile, typePair, valueResolverExpr, Context,
propertyMap, destValueExpr)
: ContextMap(typePair, valueResolverExpr, Context, destValueExpr);
valueResolverExpr = propertyMap.ValueTransformers
.Concat(_typeMap.ValueTransformers)
.Concat(_typeMap.Profile.ValueTransformers)
.Where(vt => vt.IsMatch(propertyMap))
.Aggregate(valueResolverExpr, (current, vtConfig) => ToType(ReplaceParameters(vtConfig.TransformerExpression, ToType(current, vtConfig.ValueType)), propertyMap.DestinationPropertyType));
ParameterExpression propertyValue;
Expression setPropertyValue;
if (valueResolverExpr == resolvedValue)
{
propertyValue = resolvedValue;
setPropertyValue = setResolvedValue;
}
else
{
propertyValue = Variable(valueResolverExpr.Type, "propertyValue");
setPropertyValue = Assign(propertyValue, valueResolverExpr);
}
Expression mapperExpr;
if (propertyMap.DestinationProperty is FieldInfo)
{
mapperExpr = propertyMap.SourceType != propertyMap.DestinationPropertyType
? Assign(destMember, ToType(propertyValue, propertyMap.DestinationPropertyType))
: Assign(getter, propertyValue);
}
else
{
var setter = ((PropertyInfo) propertyMap.DestinationProperty).GetSetMethod(true);
if (setter == null)
mapperExpr = propertyValue;
else
mapperExpr = Assign(destMember, ToType(propertyValue, propertyMap.DestinationPropertyType));
}
if (propertyMap.Condition != null)
mapperExpr = IfThen(
propertyMap.Condition.ConvertReplaceParameters(
Source,
_destination,
ToType(propertyValue, propertyMap.Condition.Parameters[2].Type),
ToType(getter, propertyMap.Condition.Parameters[2].Type),
Context
),
mapperExpr
);
mapperExpr = Block(new[] {setResolvedValue, setPropertyValue, mapperExpr}.Distinct());
if (propertyMap.PreCondition != null)
mapperExpr = IfThen(
propertyMap.PreCondition.ConvertReplaceParameters(Source, Context),
mapperExpr
);
return Block(new[] {resolvedValue, propertyValue}.Distinct(), mapperExpr);
}
private Expression BuildValueResolverFunc(PropertyMap propertyMap, Expression destValueExpr)
{
Expression valueResolverFunc;
var destinationPropertyType = propertyMap.DestinationPropertyType;
var valueResolverConfig = propertyMap.ValueResolverConfig;
var typeMap = propertyMap.TypeMap;
if (valueResolverConfig != null)
{
valueResolverFunc = ToType(BuildResolveCall(destValueExpr, valueResolverConfig),
destinationPropertyType);
}
else if (propertyMap.CustomResolver != null)
{
valueResolverFunc =
propertyMap.CustomResolver.ConvertReplaceParameters(Source, _destination, destValueExpr, Context);
}
else if (propertyMap.CustomExpression != null)
{
var nullCheckedExpression = propertyMap.CustomExpression.ReplaceParameters(Source)
.NullCheck(destinationPropertyType);
var destinationNullable = destinationPropertyType.IsNullableType();
var returnType = destinationNullable && destinationPropertyType.GetTypeOfNullable() ==
nullCheckedExpression.Type
? destinationPropertyType
: nullCheckedExpression.Type;
valueResolverFunc =
TryCatch(
ToType(nullCheckedExpression, returnType),
Catch(typeof(NullReferenceException), Default(returnType)),
Catch(typeof(ArgumentNullException), Default(returnType))
);
}
else if (propertyMap.SourceMembers.Any()
&& propertyMap.SourceType != null
)
{
var last = propertyMap.SourceMembers.Last();
if (last is PropertyInfo pi && pi.GetGetMethod(true) == null)
{
valueResolverFunc = Default(last.GetMemberType());
}
else
{
valueResolverFunc = Chain(propertyMap.SourceMembers, destinationPropertyType);
}
}
else if (propertyMap.SourceMember != null)
{
valueResolverFunc = MakeMemberAccess(Source, propertyMap.SourceMember);
}
else
{
valueResolverFunc = Throw(Constant(new Exception("I done blowed up")));
}
if (propertyMap.NullSubstitute != null)
{
var nullSubstitute = Constant(propertyMap.NullSubstitute);
valueResolverFunc = Coalesce(valueResolverFunc, ToType(nullSubstitute, valueResolverFunc.Type));
}
else if (!typeMap.Profile.AllowNullDestinationValues)
{
var toCreate = propertyMap.SourceType ?? destinationPropertyType;
if (!toCreate.IsAbstract() && toCreate.IsClass())
valueResolverFunc = Coalesce(
valueResolverFunc,
ToType(DelegateFactory.GenerateNonNullConstructorExpression(toCreate), propertyMap.SourceType)
);
}
return valueResolverFunc;
}
private Expression Chain(IEnumerable<MemberInfo> members, Type destinationType) =>
members
.Aggregate(
(Expression) Source,
(inner, getter) => getter is MethodInfo method ?
(getter.IsStatic() ? Call(null, method, inner) : (Expression) Call(inner, method)) :
MakeMemberAccess(getter.IsStatic() ? null : inner, getter))
.NullCheck(destinationType);
private Expression CreateInstance(Type type)
=> Call(Property(Context, nameof(ResolutionContext.Options)),
nameof(IMappingOperationOptions.CreateInstance), new[] {type});
private Expression BuildResolveCall(Expression destValueExpr, ValueResolverConfiguration valueResolverConfig)
{
var resolverInstance = valueResolverConfig.Instance != null
? Constant(valueResolverConfig.Instance)
: CreateInstance(valueResolverConfig.ConcreteType);
var sourceMember = valueResolverConfig.SourceMember?.ReplaceParameters(Source) ??
(valueResolverConfig.SourceMemberName != null
? PropertyOrField(Source, valueResolverConfig.SourceMemberName)
: null);
var iResolverType = valueResolverConfig.InterfaceType;
var parameters = new[] {Source, _destination, sourceMember, destValueExpr}.Where(p => p != null)
.Zip(iResolverType.GetGenericArguments(), ToType)
.Concat(new[] {Context});
return Call(ToType(resolverInstance, iResolverType), iResolverType.GetDeclaredMethod("Resolve"),
parameters);
}
}
}