using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Quadarax.Foundation.Core.Mapper
{
///
/// Mapping builder.
///
/// The source type.
/// The destination type.
public class LoomBuilder
where TFrom : class
where TTo : class, new()
{
private ParameterExpression sourceObj;
private ParameterExpression destObj;
private List rules;
private Loom loom;
///
/// Initializes a new instance of this class.
///
public LoomBuilder(Loom loom)
{
this.loom = loom;
sourceObj = Expression.Variable(typeof(TFrom), "source");
destObj = Expression.Variable(typeof(TTo), "dest");
rules = new List();
}
internal class ParameterReplacer : ExpressionVisitor
{
private ParameterExpression oldParam;
private ParameterExpression newParam;
///
/// Initializes a new instance of this class.
///
public ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam)
{
this.oldParam = oldParam;
this.newParam = newParam;
}
public static Expression Replace(Expression expr, ParameterExpression oldParam, ParameterExpression newParam)
{
return new ParameterReplacer(oldParam, newParam).Visit(expr);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Name.Equals(oldParam.Name) ? newParam : node;
}
}
public LoomBuilder Custom(Expression> destSet, Expression> sourceGet)
{
if (sourceGet == null) throw new ArgumentNullException("sourceGet");
if (destSet == null) throw new ArgumentNullException("destSet");
var rule = Expression.Assign(
ParameterReplacer.Replace(destSet.Body, destSet.Parameters.Single(), destObj),
ParameterReplacer.Replace(sourceGet.Body, sourceGet.Parameters.Single(), sourceObj));
rules.Add(rule);
return this;
}
public LoomBuilder PublicProperties()
{
var sourceProps = typeof(TFrom).GetProperties(BindingFlags.Public | BindingFlags.Instance);
var destProps = typeof(TTo).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var sourceProp in sourceProps.Where(p => p.CanRead && p.GetGetMethod() != null))
{
var destProp = destProps.FirstOrDefault(p => p.Name == sourceProp.Name && p.CanWrite && p.GetSetMethod() != null);
if (destProp != null)
{
if (destProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
{
// convert the source property's value to the destination type: required to assign into a Nullable<> destination property, and won't hurt in other cases
rules.Add(Expression.Assign(Expression.Property(destObj, destProp), Expression.Convert(Expression.Property(sourceObj, sourceProp), destProp.PropertyType)));
}
else
{
// source may be Nullable
Type sourceNullabeType = Nullable.GetUnderlyingType(sourceProp.PropertyType);
if (sourceNullabeType != null && destProp.PropertyType.IsAssignableFrom(sourceNullabeType))
{
rules.Add(Expression.IfThen(
Expression.Property(Expression.Property(sourceObj, sourceProp), "HasValue"),
Expression.Assign(Expression.Property(destObj, destProp), Expression.Property(Expression.Property(sourceObj, sourceProp), "Value"))));
}
}
}
}
return this;
}
public Func Compile()
{
var parameters = new[] {sourceObj, destObj};
var body = Expression.Block(rules);
var map = Expression.Lambda>(body, parameters).Compile();
loom.Define(map);
return loom.Map;
}
}
}