Skip to content

Commit

Permalink
support unmatched dimension
Browse files Browse the repository at this point in the history
  • Loading branch information
chaowlert committed Jun 11, 2017
1 parent 9b72411 commit 732f69f
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 27 deletions.
70 changes: 70 additions & 0 deletions src/Mapster.Tests/Diagnostics/DebugInfoInjectorEx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using ExpressionDebugger;
using System;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace Mapster.Diagnostics
{
public class DebugInfoInjectorEx: DebugInfoInjector
{
public DebugInfoInjectorEx(string filename): base (filename) { }

public DebugInfoInjectorEx(TextWriter writer): base(writer) { }

protected override Expression VisitConstant(ConstantExpression node)
{
node = (ConstantExpression)base.VisitConstant(node);

if (CanEmitConstant(node.Value, node.Type))
return node;

return GetNonPublicObject(node.Value, node.Type);
}

private static Expression GetNonPublicObject(object value, Type type)
{
var i = GlobalReference.GetIndex(value);
return Expression.Convert(
Expression.Call(
typeof(GlobalReference).GetMethod(nameof(GlobalReference.GetObject)),
Expression.Constant(i)),
type);
}

private static bool CanEmitConstant(object value, Type type)
{
if (value == null
|| type.IsPrimitive
|| type == typeof(string)
|| type == typeof(decimal))
return true;

if (value is Type t)
return IsVisible(t);

if (value is MethodBase mb)
return IsVisible(mb);

return false;
}

private static bool IsVisible(Type t)
{
return t is TypeBuilder
|| t.IsGenericParameter
|| t.IsVisible;
}

private static bool IsVisible(MethodBase mb)
{
if (mb is DynamicMethod || !mb.IsPublic)
return false;

Type dt = mb.DeclaringType;
return dt == null || IsVisible(dt);
}
}

}
30 changes: 30 additions & 0 deletions src/Mapster.Tests/Diagnostics/GlobalReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;

namespace ExpressionDebugger
{
public static class GlobalReference
{
private static readonly List<object> _references = new List<object>();
private static readonly Dictionary<object, int> _dict = new Dictionary<object, int>();

public static int GetIndex(object obj)
{
if (_dict.TryGetValue(obj, out var id))
return id;
lock (_references)
{
if (_dict.TryGetValue(obj, out id))
return id;
id = _references.Count;
_references.Add(obj);
_dict[obj] = id;
return id;
}
}

public static object GetObject(int i)
{
return _references[i];
}
}
}
69 changes: 69 additions & 0 deletions src/Mapster.Tests/Diagnostics/TypeAdapterConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Mapster.Diagnostics;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace Mapster
{
public static class TypeAdapterConfigExtensions
{
static Dictionary<string, int> _registeredFilename;

[Conditional("DEBUG")]
public static void EnableDebugging(this TypeAdapterConfig config, string sourceCodePath = null)
{
if (sourceCodePath == null)
sourceCodePath = GetDefaultSourceCodePath();

//initialize on first call
if (_registeredFilename == null)
{
_registeredFilename = new Dictionary<string, int>();
_assemblyName = GetAssemblyName();
}

config.Compiler = lambda =>
{
var filename = lambda.Parameters[0].Type.Name + "-" + lambda.ReturnType.Name;
var key = filename;
lock (_registeredFilename)
{
if (!_registeredFilename.TryGetValue(key, out var num))
_registeredFilename[key] = 0;
else
filename += "-" + num;
_registeredFilename[key]++;
}
using (var injector = new DebugInfoInjectorEx(Path.Combine(sourceCodePath, filename + ".cs")))
{
return injector.Compile(lambda, _assemblyName);
}
};
}

private static string GetDefaultSourceCodePath()
{
var dir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Mapster");
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
return dir;
}

static AssemblyName _assemblyName;
private static AssemblyName GetAssemblyName()
{
StrongNameKeyPair kp;
// Getting this from a resource would be a good idea.
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Mapster.Tests.mock.keys"))
using (var mem = new MemoryStream())
{
stream.CopyTo(mem);
mem.Position = 0;
kp = new StrongNameKeyPair(mem.ToArray());
}
var name = "Mapster.Dynamic";
return new AssemblyName(name) { KeyPair = kp };
}
}
}
4 changes: 4 additions & 0 deletions src/Mapster.Tests/Mapster.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
<Compile Include="Classes\Customer.cs" />
<Compile Include="Classes\Product.cs" />
<Compile Include="Classes\TypeTestClass.cs" />
<Compile Include="Diagnostics\DebugInfoInjectorEx.cs" />
<Compile Include="Diagnostics\GlobalReference.cs" />
<Compile Include="Diagnostics\TypeAdapterConfigExtensions.cs" />
<Compile Include="WhenCloningConfig.cs" />
<Compile Include="WhenCompilingConfig.cs" />
<Compile Include="WhenConvertingFromObjects.cs" />
Expand Down Expand Up @@ -122,6 +125,7 @@
</ItemGroup>
<ItemGroup>
<None Include="Mapster.Tests.snk" />
<EmbeddedResource Include="mock.keys" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
Expand Down
57 changes: 48 additions & 9 deletions src/Mapster.Tests/WhenMappingArrays.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System.Collections.Generic;

Expand Down Expand Up @@ -92,6 +93,33 @@ public void List_To_Array_Is_Mapped()
target.Ints.ShouldBe(source.Ints);
}

[TestMethod]
public void List_To_Multi_Dimensional_Array_Is_Mapped()
{
var source = new List<int> {1, 2, 3, 4, 5};
var target = source.Adapt<int[,,]>();
target.GetLength(0).ShouldBe(1);
target.GetLength(1).ShouldBe(1);
target.GetLength(2).ShouldBe(5);
}

[TestMethod]
public void Can_Map_Multi_Dimensional_Array_Of_Poco()
{
var source = new [,] {{new SimplePoco {Id = Guid.NewGuid(), Name = "Test"}}};
var target = source.Adapt<SimpleDto[,]>();
target[0, 0].Id.ShouldBe(source[0, 0].Id);
}

[TestMethod]
public void Unmatch_Rank_Is_Mapped()
{
var source = new[] {1, 2, 3, 4, 5};
var target = source.Adapt<int[,]>();
target.GetLength(0).ShouldBe(1);
target.GetLength(1).ShouldBe(5);
}

[TestMethod]
public void Array_To_List_Is_Mapped()
{
Expand All @@ -107,49 +135,60 @@ public void Array_To_List_Is_Mapped()

#region TestClasses

private class FooArray
internal class FooArray
{
public int[] Ints { get; set; }
}

private class BarArray
internal class BarArray
{
public int[] Ints { get; set; }
}

private class FooList
internal class FooList
{
public List<int> Ints { get; set; }
}

private class BarList
internal class BarList
{
public List<int> Ints { get; set; }
}

private class FooArrayMultiDimensional
internal class FooArrayMultiDimensional
{
public int[,] IntsRank2 { get; set; }
public int[,,] IntsRank3 { get; set; }
}

private class BarArrayMultiDimensional
internal class BarArrayMultiDimensional
{
public int[,] IntsRank2 { get; set; }
public int[,,] IntsRank3 { get; set; }
}

private class FooArrayJagged
internal class FooArrayJagged
{
public int[][] IntsRank2 { get; set; }
public int[][][] IntsRank3 { get; set; }
}

private class BarArrayJagged
internal class BarArrayJagged
{
public int[][] IntsRank2 { get; set; }
public int[][][] IntsRank3 { get; set; }
}
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; }
}

#endregion

Expand Down
Binary file added src/Mapster.Tests/mock.keys
Binary file not shown.
3 changes: 2 additions & 1 deletion src/Mapster/Adapters/ArrayAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ protected override Expression CreateInstantiationExpression(Expression source, E
protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg)
{
if (source.Type.IsArray &&
source.Type.GetArrayRank() == 1 &&
source.Type.GetElementType() == destination.Type.GetElementType() &&
source.Type.GetElementType().UnwrapNullable().IsConvertible())
source.Type.GetElementType().IsPrimitiveKind())
{
//Array.Copy(src, 0, dest, 0, src.Length)
var method = typeof(Array).GetMethod("Copy", new[] { typeof(Array), typeof(int), typeof(Array), typeof(int), typeof(int) });
Expand Down
39 changes: 29 additions & 10 deletions src/Mapster/Adapters/MultiDimensionalArrayAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Mapster.Utils;

namespace Mapster.Adapters
Expand All @@ -25,23 +26,41 @@ protected override bool CanInline(Expression source, Expression destination, Com

protected override Expression CreateInstantiationExpression(Expression source, Expression destination, CompileArgument arg)
{
return Expression.NewArrayBounds(arg.DestinationType.GetElementType(), GetArrayBounds(source));
return Expression.NewArrayBounds(arg.DestinationType.GetElementType(), GetArrayBounds(source, arg.DestinationType));
}

private static IEnumerable<Expression> GetArrayBounds(Expression source)
private static IEnumerable<Expression> GetArrayBounds(Expression source, Type destinationType)
{
var method = typeof(Array).GetMethod("GetLength", new[] { typeof(int) });
for (int i = 0; i < source.Type.GetArrayRank(); i++)
var destRank = destinationType.GetArrayRank();
if (!source.Type.IsArray)
{
for (int i = 0; i < destRank - 1; i++)
{
yield return Expression.Constant(1);
}
yield return ExpressionEx.CreateCountExpression(source, true);
}
else
{
yield return Expression.Call(source, method, Expression.Constant(i));
var srcRank = source.Type.GetArrayRank();
var method = typeof(Array).GetMethod("GetLength", new[] {typeof(int)});
for (int i = srcRank; i < destRank; i++)
{
yield return Expression.Constant(1);
}
for (int i = 0; i < srcRank; i++)
{
yield return Expression.Call(source, method, Expression.Constant(i));
}
}
}

protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg)
{
if (source.Type.IsArray &&
source.Type.GetArrayRank() == destination.Type.GetArrayRank() &&
source.Type.GetElementType() == destination.Type.GetElementType() &&
source.Type.GetElementType().UnwrapNullable().IsConvertible())
source.Type.GetElementType().IsPrimitiveKind())
{
//Array.Copy(src, 0, dest, 0, src.Length)
var method = typeof(Array).GetMethod("Copy", new[] { typeof(Array), typeof(int), typeof(Array), typeof(int), typeof(int) });
Expand Down Expand Up @@ -89,19 +108,19 @@ private Expression CreateArraySet(Expression source, Expression destination, Com
vlen,
Expression.Call(destination, method, Expression.Constant(i)))));
var getter = CreateAdaptExpression(item, destinationElementType, arg);
var set = Expression.Assign(
var set = ExpressionEx.Assign(
Expression.ArrayAccess(destination, vx),
getter);

Expression ifExpr = Expression.Block(
Expression.Assign(vx[1], Expression.Constant(0)),
Expression.Increment(vx[0]));
Expression.PostIncrementAssign(vx[0]));
for (var i = 1; i < vx.Count; i++)
{
var list = new List<Expression>();
if (i + 1 < vx.Count)
list.Add(Expression.Assign(vx[i + 1], Expression.Constant(0)));
list.Add(Expression.Increment(vx[i]));
list.Add(Expression.PostIncrementAssign(vx[i]));
list.Add(Expression.IfThen(
Expression.GreaterThanOrEqual(vx[i], vlenx[i]),
ifExpr));
Expand All @@ -110,7 +129,7 @@ private Expression CreateArraySet(Expression source, Expression destination, Com

var loop = ExpressionEx.ForLoop(source, item, set, ifExpr);
block.Add(loop);
return Expression.Block(vx, block);
return Expression.Block(vx.Concat(vlenx), block);
}
}
}
Loading

0 comments on commit 732f69f

Please sign in to comment.