Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
maksimkim committed Aug 20, 2013
1 parent bb3d7aa commit 611e9c8
Show file tree
Hide file tree
Showing 46 changed files with 3,180 additions and 0 deletions.
26 changes: 26 additions & 0 deletions EntityFunctors.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFunctors", "src\EntityFunctors\EntityFunctors.csproj", "{4AAE6600-6894-4F00-9F2A-E336590E6A62}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFunctors.Tests", "test\EntityFunctors.Tests\EntityFunctors.Tests.csproj", "{E750C699-1EA7-4B68-A384-F47744BA224D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4AAE6600-6894-4F00-9F2A-E336590E6A62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4AAE6600-6894-4F00-9F2A-E336590E6A62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4AAE6600-6894-4F00-9F2A-E336590E6A62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4AAE6600-6894-4F00-9F2A-E336590E6A62}.Release|Any CPU.Build.0 = Release|Any CPU
{E750C699-1EA7-4B68-A384-F47744BA224D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E750C699-1EA7-4B68-A384-F47744BA224D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E750C699-1EA7-4B68-A384-F47744BA224D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E750C699-1EA7-4B68-A384-F47744BA224D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
28 changes: 28 additions & 0 deletions src/EntityFunctors/Associations/CollectionAssociation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace EntityFunctors.Associations
{
using System;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using Extensions;

public class CollectionAssociation<TSource, TTarget> : CollectionAssociationBase<TSource, TTarget>
{
public LambdaExpression Converter { get; private set; }

public CollectionAssociation(PropertyPart source, PropertyPart target, LambdaExpression converter)
: base(source, target)
{
Contract.Assert(converter != null);
Contract.Assert(converter.Parameters.Count == 1);
Contract.Assert(converter.Parameters[0].Type == source.Property.PropertyType.GetItemType());
Contract.Assert(converter.ReturnType == target.Property.PropertyType.GetItemType());

Converter = converter;
}

protected override LambdaExpression CreateSelector(Type @from, Type to, ParameterExpression expands, IMappingRegistry registry)
{
return Converter;
}
}
}
120 changes: 120 additions & 0 deletions src/EntityFunctors/Associations/CollectionAssociationBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
namespace EntityFunctors.Associations
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Extensions;

public abstract class CollectionAssociationBase<TSource, TTarget> : IExpandable, IMappingAssociation
{
private static readonly MethodInfo ToArray;

private static readonly MethodInfo Select;

public PropertyPart Source { get; private set; }

public PropertyPart Target { get; private set; }

public MappingDirection Direction
{
get { return MappingDirection.Read; }
}

protected string Expand { get; set; }

static CollectionAssociationBase()
{
Expression<Func<IEnumerable<int>, IEnumerable<int>>> exp = xs => xs.Select(x => x).ToArray();

ToArray = ((MethodCallExpression)exp.Body).Method.GetGenericMethodDefinition();

Select = ((MethodCallExpression)((MethodCallExpression)exp.Body).Arguments[0]).Method.GetGenericMethodDefinition();
}

protected CollectionAssociationBase(PropertyPart source, PropertyPart target)
{
Contract.Assert(source != null);
Contract.Assert(target != null);

var propTarget = target.Property;

if (!(propTarget.PropertyType.IsGenericType && propTarget.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
throw new InvalidOperationException(
string.Format(
"Collection mapping supports only IEnumerable<T> type for target property {0} but found {1}",
typeof(TTarget).Name + "." + propTarget.Name,
propTarget.PropertyType
)
);

Target = target;

Source = source;
}

public virtual Expression BuildMapper(ParameterExpression @from, ParameterExpression to, IMappingRegistry registry, ParameterExpression expands)
{
if (!(@from.Type == typeof(TSource) && to.Type == typeof(TTarget)))
return Expression.Empty();

var propFrom = Expression.Property(@from, Source.Property);
var propTo = Expression.Property(to, Target.Property);

var itemTypeFrom = Source.Property.PropertyType.GetItemType();
var itemTypeTo = Target.Property.PropertyType.GetItemType();

Expression mapper = Expression.Assign(
propTo,
Expression.Condition(
propFrom.CreateCheckForDefault(),
Target.Property.PropertyType.GetDefaultExpression(),
Expression.Convert(
Expression.Call(
ToArray.MakeGenericMethod(itemTypeTo),
Expression.Call(
Select.MakeGenericMethod(itemTypeFrom, itemTypeTo),
propFrom,
CreateSelector(itemTypeFrom, itemTypeTo, expands, registry)
)
),
Target.Property.PropertyType
)
)
);

if (expands != null && !string.IsNullOrWhiteSpace(Expand))
mapper =
Expression.IfThen(
expands.CreateContains(Expression.Constant(Expand, typeof(string))),
mapper
);

return mapper;
}

public PropertyInfo RewritableProperty
{
get { return Target.Property; }
}

public Expression Rewrite(Expression original, ParameterExpression parameter)
{
return Expression.Property(parameter, Source.Property);
}

public IEnumerable<KeyValuePair<PropertyInfo, Delegate>> ValueConverters
{
get { yield break; }
}

protected abstract LambdaExpression CreateSelector(Type @from, Type to, ParameterExpression expands, IMappingRegistry registry);

public void Expandable()
{
Expand = Target.Property.GetContractName();
}
}
}
49 changes: 49 additions & 0 deletions src/EntityFunctors/Associations/ComponentCollectionAssociation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace EntityFunctors.Associations
{
using System;
using System.Linq.Expressions;

public class ComponentCollectionAssociation<TSource, TTarget> : CollectionAssociationBase<TSource, TTarget>
{
public ComponentCollectionAssociation(PropertyPart source, PropertyPart target)
:base(source, target)
{

}

protected override LambdaExpression CreateSelector(Type @from, Type to, ParameterExpression expands, IMappingRegistry registry)
{
var paramFrom = Expression.Variable(@from);
var varTo = Expression.Variable(to);

var ctor = to.GetConstructor(Type.EmptyTypes);

if (ctor == null)
throw new InvalidOperationException(string.Format(
"Type {0} must declare public parameterless constructor to be mapped from {1}",
to,
@from
));

//todo: filter out expands
var mapper = registry.GetMapper(paramFrom, varTo, expands);

if (mapper == null)
throw new InvalidOperationException(string.Format(
"Component collection registration mapping {0} <--> {1} requires mapping for types {2} <--> {3} that wasn't found",
typeof(TSource).Name + "." + Source.Property.Name,
typeof(TTarget).Name + "." + Target.Property.Name,
@from,
to
));

return Expression.Lambda(
Expression.Block(
new[] { varTo },
new[] { Expression.Assign(varTo, Expression.New(ctor)), mapper, varTo }
),
paramFrom
);
}
}
}
151 changes: 151 additions & 0 deletions src/EntityFunctors/Associations/ComponentToComponentAssociation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
namespace EntityFunctors.Associations
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Reflection;
using Extensions;

public class ComponentToComponentAssociation<TSource, TTarget> : IAccessable, IExpandable, IMappingAssociation
{
public PropertyPart Source { get; private set; }

public PropertyPart Target { get; private set; }

public MappingDirection Direction { get; private set; }

protected string Expand { get; set; }

public ComponentToComponentAssociation(PropertyPart source, PropertyPart target)
{
Contract.Assert(source != null);
Contract.Assert(target != null);

Source = source;
Target = target;

Direction = MappingDirection.All;
}

public Expression BuildMapper(ParameterExpression @from, ParameterExpression to, IMappingRegistry registry, ParameterExpression expands)
{
Contract.Assert(@from.Type == typeof(TSource) || @from.Type == typeof(TTarget));
Contract.Assert(to.Type == typeof(TSource) || to.Type == typeof(TTarget));

var direction = @from.Type == typeof(TTarget) ? MappingDirection.Write : MappingDirection.Read;

if ((Direction & direction) != direction)
return Expression.Empty();

var partFrom = direction == MappingDirection.Write ? Target : Source;
var partTo = direction == MappingDirection.Write ? Source : Target;

var propFrom = Expression.Property(@from, partFrom.Property);
var propTo = Expression.Property(to, partTo.Property);

var typeFrom = partFrom.Property.PropertyType;
var typeTo = partTo.Property.PropertyType;

var varFrom = Expression.Variable(typeFrom);
var varTo = Expression.Variable(typeTo);

//todo: filter out expands
var mapper = registry.GetMapper(varFrom, varTo, expands);

if (mapper == null)
throw new InvalidOperationException(string.Format(
"Component registration mapping {0} <--> {1} requires mapping for types {2} <--> {3} that wasn't found",
typeof(TSource).Name + "." + Source.Property.Name,
typeof(TTarget).Name + "." + Target.Property.Name,
Source.Property.PropertyType,
Target.Property.PropertyType
));

var body = new List<Expression>();

if (direction == MappingDirection.Read)
{
//create target object (only direct mapping: entity -> dto)
var ctor = typeTo.GetConstructor(Type.EmptyTypes);

if (ctor == null)
throw new InvalidOperationException(string.Format(
"Type {0} must declare public parameterless constructor to be mapped from {1}",
typeTo,
typeFrom
));

body.Add(Expression.Assign(propTo, Expression.New(ctor)));
}

//variable assignments
body.Add(Expression.Assign(varFrom, propFrom));
body.Add(Expression.Assign(varTo, propTo));

body.Add(mapper);

var result =
Expression.IfThenElse(
propFrom.CreateCheckForDefault(),
Expression.Assign(propTo, typeTo.GetDefaultExpression()),
Expression.Block(new[] {varFrom, varTo}, body)
);

if (direction == MappingDirection.Read && expands != null && !string.IsNullOrWhiteSpace(Expand))
result = Expression.IfThen(
expands.CreateContains(Expression.Constant(Expand, typeof(string))),
result
);

return result;

}

public PropertyInfo RewritableProperty
{
get { return Target.Property; }
}

public Expression Rewrite(Expression original, ParameterExpression parameter)
{
throw new NotImplementedException();
}

public IEnumerable<KeyValuePair<PropertyInfo, Delegate>> ValueConverters
{
get { yield break; }
}

public void ReadOnly()
{
Direction = MappingDirection.Read;
}

public void WriteOnly()
{
Direction = MappingDirection.Write;
}

public void Read()
{
AddDirection(MappingDirection.Read);
}

public void Write()
{
AddDirection(MappingDirection.Write);
}

private void AddDirection(MappingDirection val)
{
if ((Direction & val) != val)
Direction |= val;
}

public void Expandable()
{
Expand = Target.Property.GetContractName();
}
}
}
Loading

0 comments on commit 611e9c8

Please sign in to comment.