Refactor more mappers

This commit is contained in:
Stephan
2019-03-20 19:09:23 +01:00
parent cd0338498f
commit a6dc5f0eac
9 changed files with 352 additions and 80 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -11,7 +12,8 @@ namespace Umbraco.Core.Mapping
public class Mapper
{
private readonly Dictionary<Type, Dictionary<Type, Func<object, object>>> _maps = new Dictionary<Type, Dictionary<Type, Func<object, object>>>();
private readonly Dictionary<Type, Dictionary<Type, Func<object, object>>> _ctors = new Dictionary<Type, Dictionary<Type, Func<object, object>>>();
private readonly Dictionary<Type, Dictionary<Type, Action<object, object>>> _maps = new Dictionary<Type, Dictionary<Type, Action<object, object>>>();
public Mapper(MapperProfileCollection profiles)
{
@@ -19,15 +21,29 @@ namespace Umbraco.Core.Mapping
profile.SetMaps(this);
}
public void SetMap<TSource, TTarget>(Func<TSource, TTarget> map)
public void SetMap<TSource, TTarget>()
=> SetMap<TSource, TTarget>((source, target) => { });
public void SetMap<TSource, TTarget>(Action<TSource, TTarget> map)
=> SetMap(source => throw new NotSupportedException($"Don't know how to create {typeof(TTarget)} instances."), map);
public void SetMap<TSource, TTarget>(Func<TSource, TTarget> ctor)
=> SetMap(ctor, (source, target) => { });
public void SetMap<TSource, TTarget>(Func<TSource, TTarget> ctor, Action<TSource, TTarget> map)
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
if (!_maps.TryGetValue(sourceType, out var sourceMap))
sourceMap = _maps[sourceType] = new Dictionary<Type, Func<object, object>>();
if (!_ctors.TryGetValue(sourceType, out var sourceCtor))
sourceCtor = _ctors[sourceType] = new Dictionary<Type, Func<object, object>>();
sourceMap[targetType] = o => map((TSource)o);
sourceCtor[targetType] = source => ctor((TSource) source);
if (!_maps.TryGetValue(sourceType, out var sourceMap))
sourceMap = _maps[sourceType] = new Dictionary<Type, Action<object, object>>();
sourceMap[targetType] = (source, target) => map((TSource) source, (TTarget) target);
}
public TTarget Map<TTarget>(object source)
@@ -35,18 +51,161 @@ namespace Umbraco.Core.Mapping
if (source == null)
throw new ArgumentNullException(nameof(source));
var sourceType = source.GetType();
var targetType = typeof(TTarget);
var ctor = GetCtor(sourceType, typeof(TTarget));
var map = GetMap(sourceType, typeof(TTarget));
if (ctor != null && map != null)
{
var target = ctor(source);
map(source, target);
return (TTarget)target;
}
bool IsOk(Type type)
{
// note: we're not going to work with just plain enumerables of anything,
// only on arrays or anything that is generic and implements IEnumerable<>
if (type.IsArray && type.GetArrayRank() == 1) return true;
if (type.IsGenericType && type.GenericTypeArguments.Length == 1) return true;
return false;
}
Type GetGenericArg(Type type)
{
if (type.IsArray) return type.GetElementType();
if (type.IsGenericType) return type.GenericTypeArguments[0];
throw new Exception("panic");
}
if (IsOk(sourceType) && IsOk(targetType))
{
var sourceGenericArg = GetGenericArg(sourceType);
var targetGenericArg = GetGenericArg(targetType);
var sourceEnumerableType = typeof(IEnumerable<>).MakeGenericType(sourceGenericArg);
var targetEnumerableType = typeof(IEnumerable<>).MakeGenericType(targetGenericArg);
if (sourceEnumerableType.IsAssignableFrom(sourceType) && targetEnumerableType.IsAssignableFrom(targetType))
{
ctor = GetCtor(sourceGenericArg, targetGenericArg);
map = GetMap(sourceGenericArg, targetGenericArg);
if (ctor != null && map != null)
{
var sourceEnumerable = (IEnumerable)source;
var targetEnumerable = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(targetGenericArg));
foreach (var sourceItem in sourceEnumerable)
{
var targetItem = ctor(sourceItem);
map(sourceItem, targetItem);
targetEnumerable.Add(targetItem);
}
return (TTarget)targetEnumerable;
}
// fixme - temp
return AutoMapper.Mapper.Map<TTarget>(source);
}
}
// fixme this is temp
//throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
return AutoMapper.Mapper.Map<TTarget>(source);
}
// TODO: when AutoMapper is completely gone these two methods can merge
public TTarget Map<TSource, TTarget>(TSource source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
var ctor = GetCtor(sourceType, typeof(TTarget));
var map = GetMap(sourceType, targetType);
if (ctor != null && map != null)
{
var target = ctor(source);
map(source, target);
return (TTarget) target;
}
if (sourceType.IsGenericType && targetType.IsGenericType)
{
var sourceGeneric = sourceType.GetGenericTypeDefinition();
var targetGeneric = targetType.GetGenericTypeDefinition();
var ienumerable = typeof(IEnumerable<>);
if (sourceGeneric == ienumerable && targetGeneric == ienumerable)
{
var sourceGenericType = sourceType.GenericTypeArguments[0];
var targetGenericType = targetType.GenericTypeArguments[0];
ctor = GetCtor(sourceGenericType, targetGenericType);
map = GetMap(sourceGenericType, targetGenericType);
if (ctor != null && map != null)
{
var sourceEnumerable = (IEnumerable)source;
var targetEnumerable = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(targetGenericType));
foreach (var sourceItem in sourceEnumerable)
{
var targetItem = ctor(sourceItem);
map(sourceItem, targetItem);
targetEnumerable.Add(targetItem);
}
return (TTarget)targetEnumerable;
}
// fixme - temp
return AutoMapper.Mapper.Map<TSource, TTarget>(source);
}
}
// fixme this is temp
//throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
return AutoMapper.Mapper.Map<TSource, TTarget>(source);
}
public TTarget Map<TSource, TTarget>(TSource source, TTarget target)
{
// fixme should we deal with enumerables?
var map = GetMap(source.GetType(), typeof(TTarget));
if (map == null)
{
// fixme this is temp
//throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
return AutoMapper.Mapper.Map<TTarget>(source);
return AutoMapper.Mapper.Map(source, target);
}
return (TTarget) map(source);
map(source, target);
return target;
}
private Func<object, object> GetMap(Type sourceType, Type targetType)
private Func<object, object> GetCtor(Type sourceType, Type targetType)
{
if (!_ctors.TryGetValue(sourceType, out var sourceCtor))
{
var type = _maps.Keys.FirstOrDefault(x => x.IsAssignableFrom(sourceType));
if (type == null)
return null;
sourceCtor = _ctors[sourceType] = _ctors[type];
}
return sourceCtor.TryGetValue(targetType, out var ctor) ? ctor : null;
}
private Action<object, object> GetMap(Type sourceType, Type targetType)
{
if (!_maps.TryGetValue(sourceType, out var sourceMap))
{
@@ -58,41 +217,5 @@ namespace Umbraco.Core.Mapping
return sourceMap.TryGetValue(targetType, out var map) ? map : null;
}
public TTarget Map<TSource, TTarget>(TSource source)
{
return AutoMapper.Mapper.Map<TSource, TTarget>(source);
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
var map = GetMap(sourceType, targetType);
if (map != null)
return (TTarget) map(source);
if (sourceType.IsGenericType && targetType.IsGenericType)
{
var sourceGeneric = sourceType.GetGenericTypeDefinition();
var targetGeneric = targetType.GetGenericTypeDefinition();
var ienumerable = typeof(IEnumerable<>);
if (sourceGeneric == ienumerable && targetGeneric == ienumerable)
{
var sourceGenericType = sourceGeneric.GetGenericArguments()[0];
var targetGenericType = targetGeneric.GetGenericArguments()[0];
map = GetMap(sourceGenericType, targetGenericType);
// fixme - how can we enumerate, generically?
}
}
// fixme this is temp
//throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
return AutoMapper.Mapper.Map<TSource, TTarget>(source);
}
public TTarget Map<TSource, TTarget>(TSource source, TTarget target)
{
return AutoMapper.Mapper.Map(source, target); // fixme what does this do exactly?
}
}
}