Skip to content

Commit

Permalink
Merge pull request MapsterMapper#46 from chaowlert/support-record-type
Browse files Browse the repository at this point in the history
Add support for record type
  • Loading branch information
eswann committed Feb 24, 2016
2 parents d82f39e + 6694a15 commit 77e49f6
Show file tree
Hide file tree
Showing 18 changed files with 649 additions and 260 deletions.
1 change: 1 addition & 0 deletions src/Mapster.Tests/Mapster.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<Compile Include="Classes\Product.cs" />
<Compile Include="Classes\TypeTestClass.cs" />
<Compile Include="WhenCreatingConfigInstance.cs" />
<Compile Include="WhenMappingRecordTypes.cs" />
<Compile Include="WhenMappingIgnoreNullValues.cs" />
<Compile Include="WhenMappingWithExtensionMethods.cs" />
<Compile Include="WhenPreserveReferences.cs" />
Expand Down
4 changes: 2 additions & 2 deletions src/Mapster.Tests/WhenMappingPrimitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ public class ImmutableB
{
public ImmutableB(string name)
{
this.Name = name;
this.NameX = name;
}

public string Name { get; }
public string NameX { get; }
}

public class TestA
Expand Down
68 changes: 68 additions & 0 deletions src/Mapster.Tests/WhenMappingRecordTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using Should;

namespace Mapster.Tests
{
[TestFixture]
public class WhenMappingRecordTypes
{
[Test]
public void Map_Dictionary()
{
var source = new Dictionary<string, SimplePoco>
{
{"a", new SimplePoco {Id = Guid.NewGuid(), Name = "bar"}}
};
var dest = source.Adapt<Dictionary<string, SimpleDto>>();

dest.Count.ShouldEqual(source.Count);
dest["a"].Id.ShouldEqual(source["a"].Id);
dest["a"].Name.ShouldEqual(source["a"].Name);
}

[Test]
public void Map_RecordType()
{
var source = new SimplePoco {Id = Guid.NewGuid(), Name = "bar"};
var dest = source.Adapt<RecordType>();

dest.Id.ShouldEqual(source.Id);
dest.Name.ShouldEqual(source.Name);
dest.Day.ShouldEqual(default(DayOfWeek));
dest.Age.ShouldEqual(10);
}

public class SimplePoco
{
public Guid Id { get; set; }
public string Name { get; set; }
}

public class SimpleDto
{
public Guid Id { get; set; }
public string Name { get; set; }
}

public class RecordType
{
public RecordType(Guid id, DayOfWeek day, string name = "foo", int age = 10)
{
this.Id = id;
this.Day = day;
this.Name = name;
this.Age = age;
}

public Guid Id { get; }
public string Name { get; }
public int Age { get; }
public DayOfWeek Day { get; }
}
}
}
190 changes: 190 additions & 0 deletions src/Mapster/Adapters/BaseClassAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Mapster.Models;
using Mapster.Utils;

namespace Mapster.Adapters
{
internal abstract class BaseClassAdapter : BaseAdapter
{
protected abstract ClassModel GetClassModel(Type destinationType);

#region Build the Adapter Model

protected ClassConverter CreateClassConverter(Expression source, Expression destination, CompileArgument arg)
{
Type sourceType = source.Type;
var classModel = GetClassModel(arg.DestinationType);
var destinationMembers = classModel.Members;

var unmappedDestinationMembers = new List<string>();

var properties = new List<MemberConverter>();

for (int i = 0; i < destinationMembers.Count; i++)
{
var destinationMember = destinationMembers[i];

if (ProcessIgnores(arg.Settings, destinationMember)) continue;
if (ProcessCustomResolvers(source, destination, arg.Settings, destinationMember, properties)) continue;

var sourceMember = ReflectionUtils.GetMemberModel(sourceType, destinationMember.Name);
if (sourceMember != null)
{
var propertyModel = new MemberConverter
{
ConvertType = 1,
Getter = sourceMember.GetExpression(source),
Setter = destinationMember.GetExpression(destination),
SetterInfo = destinationMember.Info,
};
properties.Add(propertyModel);
}
else
{
if (arg.MapType != MapType.Projection && FlattenMethod(source, destination, destinationMember, properties)) continue;

if (FlattenClass(source, destination, destinationMember, properties, arg.MapType == MapType.Projection)) continue;

if (classModel.ConstructorInfo != null)
{
var propertyModel = new MemberConverter
{
ConvertType = 0,
Getter = null,
Setter = destinationMember.GetExpression(destination),
SetterInfo = destinationMember.Info,
};
properties.Add(propertyModel);
continue;
}

if (destinationMember.HasSetter)
{
unmappedDestinationMembers.Add(destinationMember.Name);
}
}
}

if (arg.Context.Config.RequireDestinationMemberSource && unmappedDestinationMembers.Count > 0)
{
throw new ArgumentOutOfRangeException($"The following members of destination class {arg.DestinationType} do not have a corresponding source member mapped or ignored:{string.Join(",", unmappedDestinationMembers)}");
}

return new ClassConverter
{
ConstructorInfo = classModel.ConstructorInfo,
Members = properties,
};
}

private static bool FlattenClass(
Expression source,
Expression destination,
IMemberModel destinationMember,
List<MemberConverter> properties,
bool isProjection)
{
var getter = ReflectionUtils.GetDeepFlattening(source, destinationMember.Name, isProjection);
if (getter != null)
{
var propertyModel = new MemberConverter
{
ConvertType = 3,
Getter = getter,
Setter = destinationMember.GetExpression(destination),
SetterInfo = destinationMember.Info,
};
properties.Add(propertyModel);

return true;
}
return false;
}

private static bool FlattenMethod(
Expression source,
Expression destination,
IMemberModel destinationMember,
List<MemberConverter> properties)
{
var getMethod = source.Type.GetMethod(string.Concat("Get", destinationMember.Name));
if (getMethod != null)
{
var propertyModel = new MemberConverter
{
ConvertType = 2,
Getter = Expression.Call(source, getMethod),
Setter = destinationMember.GetExpression(destination),
SetterInfo = destinationMember.Info,
};

properties.Add(propertyModel);

return true;
}
return false;
}

private static bool ProcessCustomResolvers(
Expression source,
Expression destination,
TypeAdapterSettings config,
IMemberModel destinationMember,
List<MemberConverter> properties)
{
bool isAdded = false;
var resolvers = config.Resolvers;
if (resolvers != null && resolvers.Count > 0)
{
MemberConverter memberConverter = null;
LambdaExpression lastCondition = null;
for (int j = 0; j < resolvers.Count; j++)
{
var resolver = resolvers[j];
if (destinationMember.Name.Equals(resolver.MemberName))
{
if (memberConverter == null)
{
memberConverter = new MemberConverter
{
ConvertType = 5,
Setter = destinationMember.GetExpression(destination),
SetterInfo = destinationMember.Info,
};
isAdded = true;
}

Expression invoke = resolver.Invoker.Apply(source);
memberConverter.Getter = lastCondition != null
? Expression.Condition(lastCondition.Apply(source), memberConverter.Getter, invoke)
: invoke;
lastCondition = resolver.Condition;
if (resolver.Condition == null)
break;
}
}
if (memberConverter != null)
{
if (lastCondition != null)
memberConverter.Getter = Expression.Condition(lastCondition.Apply(source), memberConverter.Getter, Expression.Constant(memberConverter.Getter.Type.GetDefault(), memberConverter.Getter.Type));
properties.Add(memberConverter);
}
}
return isAdded;
}

private static bool ProcessIgnores(TypeAdapterSettings config, IMemberModel destinationMember)
{
if (config.IgnoreMembers.Contains(destinationMember.Name))
return true;
var attributes = destinationMember.GetCustomAttributes(true).Select(attr => attr.GetType());
return config.IgnoreAttributes.Overlaps(attributes);
}

#endregion
}
}
Loading

0 comments on commit 77e49f6

Please sign in to comment.