Skip to content

Commit

Permalink
Merge pull request MapsterMapper#12 from chaowlert/master
Browse files Browse the repository at this point in the history
Support more collection types + Able to set null value to nullable property + Value types will always be primitive
  • Loading branch information
eswann committed Jan 14, 2016
2 parents 8a9c441 + 6797186 commit 85ecfe1
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 35 deletions.
5 changes: 3 additions & 2 deletions src/Mapster.Tests/WhenMappingCollections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public class PersonDTO
public Guid Id { get; set; }
public string Name { get; set; }
public Projects Project { get; set; }
public List<int> X { get; set; }

public HashSet<int> X { get; set; }
public int[] Y { get; set; }
public ICollection<Guid> Z { get; set; }
public ArrayList Ids { get; set; }
Expand Down Expand Up @@ -107,7 +108,7 @@ public void MapCollectionProperty()
dto.Project == person.Project);

Assert.IsNotNull(dto.X);
Assert.IsTrue(dto.X.Count == 4 && dto.X[0] == 1 && dto.X[1] == 2 && dto.X[2] == 3 && dto.X[3] == 4);
Assert.IsTrue(dto.X.Count == 4 && dto.X.Contains(1) && dto.X.Contains(2) && dto.X.Contains(3) && dto.X.Contains(4));

Assert.IsNotNull(dto.Y);
Assert.IsTrue(dto.Y.Length == 3 && dto.Y[0] == 5 && dto.Y[1] == 6 && dto.Y[2] == 7);
Expand Down
18 changes: 18 additions & 0 deletions src/Mapster.Tests/WhenMappingNullablePrimitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ public void Can_Map_From_Nullable_Source_To_Nullable_Target()
dto.Amount.ShouldBeNull();
}

[Test]
public void Can_Map_From_Nullable_Source_To_Nullable_Existing_Target()
{
var poco = new NullablePrimitivesPoco { Id = Guid.NewGuid(), Name = "TestName" };

NullablePrimitivesPoco2 dto = new NullablePrimitivesPoco2
{
IsImport = true,
Amount = 1,
};

TypeAdapter.Adapt(poco, dto);

dto.Id.ShouldEqual(poco.Id);
dto.Name.ShouldEqual(poco.Name);
dto.IsImport.ShouldBeNull();
dto.Amount.ShouldBeNull();
}

[Test]
public void Can_Map_From_Nullable_Source_With_Values_To_Non_Nullable_Target()
Expand Down
23 changes: 23 additions & 0 deletions src/Mapster.Tests/WhenMappingPrimitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ public void Byte_Array_In_Test_Class_Is_Mapped_Correctly()
testString.ShouldEqual(resultString);
}

[Test]
public void ValueType_String_Object_Is_Always_Primitive()
{
var sourceDto = new PrimitivePoco
{
Id = "test",
Time = TimeSpan.FromHours(7),
Obj = new object(),
};
var targetDto = TypeAdapter.Adapt<PrimitivePoco, PrimitivePoco>(sourceDto);

targetDto.Id.ShouldEqual(sourceDto.Id);
targetDto.Time.ShouldEqual(sourceDto.Time);
targetDto.Obj.ShouldBeSameAs(sourceDto.Obj);
}

public class TestA
{
Expand All @@ -55,5 +70,13 @@ public class TestB
{
public Byte[] Bytes { get; set; }
}

public class PrimitivePoco
{
public string Id { get; set; }
public TimeSpan Time { get; set; }
public object Obj { get; set; }
}

}
}
26 changes: 21 additions & 5 deletions src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private static TDestination Adapt(TSource source, TDestination destination, bool
{
case 1: //Primitive
object primitiveValue = propertyModel.Getter.Invoke(source);
if (primitiveValue == null)
if (primitiveValue == null && (ignoreNullValues || propertyModel.DestinationPropertyType.IsNonNullable()))
{
continue;
}
Expand All @@ -141,6 +141,11 @@ private static TDestination Adapt(TSource source, TDestination destination, bool
break;
case 2: //Flattening Get Method
destinationValue = propertyModel.AdaptInvoker(source, null);
if (destinationValue == null &&
(ignoreNullValues || propertyModel.DestinationPropertyType.IsNonNullable()))
{
continue;
}
break;
case 3: //Flattening Deep Property
var flatInvokers = propertyModel.FlatteningInvokers;
Expand All @@ -152,15 +157,15 @@ private static TDestination Adapt(TSource source, TDestination destination, bool
break;
}

if (value == null && ignoreNullValues)
if (value == null && (ignoreNullValues || propertyModel.DestinationPropertyType.IsNonNullable()))
{
continue;
}
destinationValue = value;
break;
case 4: // Adapter
object sourceValue = propertyModel.Getter.Invoke(source);
if (sourceValue == null && ignoreNullValues)
if (sourceValue == null && (ignoreNullValues || propertyModel.DestinationPropertyType.IsNonNullable()))
{
continue;
}
Expand All @@ -177,6 +182,11 @@ private static TDestination Adapt(TSource source, TDestination destination, bool
if (propertyModel.Condition == null || propertyModel.Condition(source))
{
destinationValue = propertyModel.CustomResolver(source);
if (destinationValue == null &&
(ignoreNullValues || propertyModel.DestinationPropertyType.IsNonNullable()))
{
continue;
}
break;
}
continue;
Expand Down Expand Up @@ -303,6 +313,7 @@ private static AdapterModel<TSource, TDestination> CreateAdapterModel()
var propertyModel = propertyModelFactory();
propertyModel.Getter = getter;
propertyModel.Setter = setter;
propertyModel.DestinationPropertyType = destinationPropertyType;
propertyModel.SetterPropertyName = ExtractPropertyName(setter, "Set");
if (destinationTransforms.ContainsKey(destinationPropertyType))
propertyModel.DestinationTransform = destinationTransforms[destinationPropertyType];
Expand Down Expand Up @@ -411,12 +422,14 @@ private static bool FlattenClass(Type sourceType, MemberInfo destinationMember,
ReflectionUtils.GetDeepFlattening(sourceType, destinationMember.Name, delegates);
if (delegates.Count > 0)
{
var setter = PropertyCaller<TDestination>.CreateSetMethod((PropertyInfo)destinationMember);
var destinationProperty = (PropertyInfo) destinationMember;
var setter = PropertyCaller<TDestination>.CreateSetMethod(destinationProperty);
if (setter != null)
{
var propertyModel = (PropertyModel<TSource, TDestination>)propertyModelFactory();
propertyModel.ConvertType = 3;
propertyModel.Setter = setter;
propertyModel.DestinationPropertyType = destinationProperty.PropertyType;
propertyModel.SetterPropertyName = ExtractPropertyName(setter, "Set");
var destinationPropertyType = typeof(TDestination);
if (destinationTransforms.ContainsKey(destinationPropertyType))
Expand All @@ -438,13 +451,15 @@ private static bool FlattenMethod(Type sourceType, MemberInfo destinationMember,
var getMethod = sourceType.GetMethod(String.Concat("Get", destinationMember.Name));
if (getMethod != null)
{
var setter = PropertyCaller<TDestination>.CreateSetMethod((PropertyInfo)destinationMember);
var destinationProperty = (PropertyInfo) destinationMember;
var setter = PropertyCaller<TDestination>.CreateSetMethod(destinationProperty);
if (setter == null)
return true;

var propertyModel = (PropertyModel<TSource, TDestination>)propertyModelFactory();
propertyModel.ConvertType = 2;
propertyModel.Setter = setter;
propertyModel.DestinationPropertyType = destinationProperty.PropertyType;
propertyModel.SetterPropertyName = ExtractPropertyName(setter, "Set");
var destinationPropertyType = typeof(TDestination);
if (destinationTransforms.ContainsKey(destinationPropertyType))
Expand Down Expand Up @@ -481,6 +496,7 @@ private static bool ProcessCustomResolvers(TypeAdapterConfigSettings<TSource, TD
var propertyModel = (PropertyModel<TSource, TDestination>)propertyModelFactory();
propertyModel.ConvertType = 5;
propertyModel.Setter = setter;
propertyModel.DestinationPropertyType = destinationProperty.PropertyType;
propertyModel.SetterPropertyName = ExtractPropertyName(setter, "Set");
propertyModel.CustomResolver = resolver.Invoker;
propertyModel.Condition = resolver.Condition;
Expand Down
28 changes: 20 additions & 8 deletions src/Mapster/Adapters/CollectionAdapter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using Mapster.Models;
using Mapster.Utils;
Expand Down Expand Up @@ -61,7 +62,7 @@ public static object Adapt(TSource source, object destination, Dictionary<long,
{
#region CopyToArray

byte i = 0;
int i = 0;
var adapterInvoker = _collectionAdapterModel.AdaptInvoker;
var array = destination == null ? new TDestinationElement[((ICollection)source).Count] : (TDestinationElement[])destination;
if (_collectionAdapterModel.IsPrimitive)
Expand Down Expand Up @@ -91,21 +92,32 @@ public static object Adapt(TSource source, object destination, Dictionary<long,

#endregion
}

if (destinationType.IsGenericType)

var canInstantiate = !destinationType.IsInterface && typeof (ICollection<TDestinationElement>).IsAssignableFrom(destinationType);
if (canInstantiate || destinationType.IsAssignableFrom(typeof(List<TDestinationElement>)))
{
#region CopyToList

var adapterInvoker = _collectionAdapterModel.AdaptInvoker;
var list = destination == null ? new List<TDestinationElement>() : (List<TDestinationElement>)destination;
ICollection<TDestinationElement> list;
if (destination == null)
{
list = canInstantiate
? (ICollection<TDestinationElement>) ActivatorExtensions.CreateInstance(destinationType)
: new List<TDestinationElement>();
}
else
{
list = (ICollection<TDestinationElement>)destination;
}
if (_collectionAdapterModel.IsPrimitive)
{
bool hasInvoker = adapterInvoker != null;
foreach (var item in source)
{
if (item == null)
list.Add(default(TDestinationElement));
else if(hasInvoker)
else if (hasInvoker)
list.Add((TDestinationElement)adapterInvoker(null, new[] { item }));
else
list.Add((TDestinationElement)item);
Expand All @@ -124,12 +136,12 @@ public static object Adapt(TSource source, object destination, Dictionary<long,
#endregion
}

if (destinationType == typeof(ArrayList))
if (!destinationType.IsInterface && typeof(IList).IsAssignableFrom(destinationType))
{
#region CopyToArrayList

var adapterInvoker = _collectionAdapterModel.AdaptInvoker;
var array = destination == null ? new ArrayList() : (ArrayList)destination;
var array = destination == null ? (IList)ActivatorExtensions.CreateInstance(destinationType) : (IList)destination;
if (_collectionAdapterModel.IsPrimitive)
{
bool hasInvoker = adapterInvoker != null;
Expand Down
2 changes: 1 addition & 1 deletion src/Mapster/Models/PropertyModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ public class PropertyModel<TSource, TDestination>

public string SetterPropertyName;


public Type DestinationPropertyType;
}
}
2 changes: 1 addition & 1 deletion src/Mapster/Utils/Activator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static Delegate GetConstructorDelegate(this Type type, Type delegateType)
type.Name));
}

var dynamicMethod = new DynamicMethod("DM$_" + type.Name, type, argTypes, type);
var dynamicMethod = new DynamicMethod("DM$_" + type.Name, type, argTypes);
ILGenerator ilGen = dynamicMethod.GetILGenerator();
for (int i = 0; i < argTypes.Length; i++)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Mapster/Utils/FastObjectFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static Func<T> CreateObjectFactory<T>(Func<T> factory = null)
}
else
{
var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + type.Name, type, null, type);
var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + type.Name, type, null);
ILGenerator ilGen = dynMethod.GetILGenerator();

ilGen.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
Expand Down
35 changes: 18 additions & 17 deletions src/Mapster/Utils/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ internal static class ReflectionUtils
private static readonly Type _nullableType = typeof (Nullable<>);

private static readonly Type _iEnumerableType = typeof(IEnumerable);
private static readonly Type _arrayListType = typeof(ArrayList);

public static bool IsNullable(this Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == _nullableType;
}

public static bool IsNonNullable(this Type type)
{
return type.IsValueType && !type.IsNullable();
}

public static List<MemberInfo> GetPublicFieldsAndProperties(this Type type, bool allowNonPublicSetter = true, bool allowNoSetter = true)
{
var results = new List<MemberInfo>();
Expand Down Expand Up @@ -87,13 +91,8 @@ public static bool IsCollection(this Type type)
public static bool IsPrimitiveRoot(this Type type)
{
return
type.IsPrimitive
type.IsValueType
|| type == typeof(string)
|| type == typeof(decimal)
|| type == typeof(DateTime)
|| type == typeof(Guid)
|| type.IsEnum
|| (IsNullable(type) && IsPrimitiveRoot(Nullable.GetUnderlyingType(type)))
|| TypeAdapterConfig.GlobalSettings.PrimitiveTypes.Contains(type)
|| type == typeof(object)
;
Expand All @@ -111,21 +110,23 @@ public static bool IsEnum(this Type type)

public static Type ExtractCollectionType(this Type collectionType)
{
if (collectionType.IsArray)
if (collectionType.IsGenericEnumerableType())
{
return collectionType.GetElementType();
return collectionType.GetGenericArguments()[0];
}
if (collectionType == _arrayListType)
var enumerableType = collectionType.GetInterfaces().FirstOrDefault(IsGenericEnumerableType);
if (enumerableType != null)
{
return typeof(object);
return enumerableType.GetGenericArguments()[0];
}
if (collectionType.IsGenericType)
{
return collectionType.GetGenericArguments()[0];
}
return collectionType;
return typeof (object);
}


public static bool IsGenericEnumerableType(this Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof (IEnumerable<>);
}

public static FastInvokeHandler CreatePrimitiveConverter(this Type sourceType, Type destinationType)
{
Type srcType;
Expand Down

0 comments on commit 85ecfe1

Please sign in to comment.