From 3b5d050353b7e766f2f763c1a6a13afd3b9e8ad0 Mon Sep 17 00:00:00 2001 From: Lauro Moura Date: Thu, 11 Jul 2019 13:36:25 -0300 Subject: [PATCH] csharp: Enable conversion of container Eina.Values Summary: When creating a new Value with any IEnumerable of a supported type, the IEnumerable will be copied into an Eina.Value of type EINA_VALUE_ARRAY_TYPE. Similarly, `Unwrap()` on a Eina.Value container will create a new System.Collections.List and return it. Depends on D9272 Reviewers: felipealmeida, vitor.sousa, segfaultxavi Reviewed By: vitor.sousa Subscribers: cedric, #reviewers, #committers Tags: #efl, #expertise_solutions Differential Revision: https://phab.enlightenment.org/D9273 --- src/bindings/mono/eina_mono/eina_value.cs | 117 +++++++++++++++++++++- src/tests/efl_mono/Value.cs | 77 ++++++++++++++ 2 files changed, 193 insertions(+), 1 deletion(-) diff --git a/src/bindings/mono/eina_mono/eina_value.cs b/src/bindings/mono/eina_mono/eina_value.cs index 37412b679b..cedf5001c2 100644 --- a/src/bindings/mono/eina_mono/eina_value.cs +++ b/src/bindings/mono/eina_mono/eina_value.cs @@ -3,6 +3,7 @@ #define CODE_ANALYSIS using System; +using System.Linq; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Security.Permissions; @@ -719,6 +720,8 @@ static class ValueTypeBridge { private static Dictionary ManagedToNative = new Dictionary(); private static Dictionary NativeToManaged = new Dictionary(); + private static Dictionary StandardToManaged = new Dictionary(); + private static Dictionary ManagedToStandard = new Dictionary(); private static bool TypesLoaded; // CLR defaults to false; public static ValueType GetManaged(IntPtr native) @@ -750,48 +753,106 @@ static class ValueTypeBridge return ManagedToNative[valueType]; } + /// Returns the Eina.Value type associated with the given C# type. + public static ValueType GetManaged(System.Type type) + { + ValueType v; + if (StandardToManaged.TryGetValue(type, out v)) + { + return v; + } + else + { + if (typeof(Efl.Object).IsAssignableFrom(type)) + { + return ValueType.Object; + } + throw new Efl.EflException($"Unknown value type mapping for C# type {type}"); + } + } + + /// Returns the System.Type associated with the given Eina.Value type. + /// The intermediate type as returned by . + /// The associated C# type with this value type. + public static System.Type GetStandard(ValueType valueType) + { + System.Type ret = null; + if (ManagedToStandard.TryGetValue(valueType, out ret)) + { + return ret; + } + else + { + throw new Efl.EflException($"Unknown C# type mapping for value type {valueType}"); + } + } + private static void LoadTypes() { Eina.Config.Init(); // Make sure eina is initialized. ManagedToNative.Add(ValueType.SByte, type_sbyte()); NativeToManaged.Add(type_sbyte(), ValueType.SByte); + StandardToManaged.Add(typeof(sbyte), ValueType.SByte); + ManagedToStandard.Add(ValueType.SByte, typeof(sbyte)); ManagedToNative.Add(ValueType.Byte, type_byte()); NativeToManaged.Add(type_byte(), ValueType.Byte); + StandardToManaged.Add(typeof(byte), ValueType.Byte); + ManagedToStandard.Add(ValueType.Byte, typeof(byte)); ManagedToNative.Add(ValueType.Short, type_short()); NativeToManaged.Add(type_short(), ValueType.Short); + StandardToManaged.Add(typeof(short), ValueType.Short); + ManagedToStandard.Add(ValueType.Short, typeof(short)); ManagedToNative.Add(ValueType.UShort, type_ushort()); NativeToManaged.Add(type_ushort(), ValueType.UShort); + StandardToManaged.Add(typeof(ushort), ValueType.UShort); + ManagedToStandard.Add(ValueType.UShort, typeof(ushort)); ManagedToNative.Add(ValueType.Int32, type_int32()); NativeToManaged.Add(type_int32(), ValueType.Int32); + StandardToManaged.Add(typeof(int), ValueType.Int32); + ManagedToStandard.Add(ValueType.Int32, typeof(int)); ManagedToNative.Add(ValueType.UInt32, type_uint32()); NativeToManaged.Add(type_uint32(), ValueType.UInt32); + StandardToManaged.Add(typeof(uint), ValueType.UInt32); + ManagedToStandard.Add(ValueType.UInt32, typeof(uint)); ManagedToNative.Add(ValueType.Long, type_long()); NativeToManaged.Add(type_long(), ValueType.Long); + ManagedToStandard.Add(ValueType.Long, typeof(long)); ManagedToNative.Add(ValueType.ULong, type_ulong()); NativeToManaged.Add(type_ulong(), ValueType.ULong); + ManagedToStandard.Add(ValueType.ULong, typeof(ulong)); ManagedToNative.Add(ValueType.Int64, type_int64()); NativeToManaged.Add(type_int64(), ValueType.Int64); + StandardToManaged.Add(typeof(long), ValueType.Int64); + ManagedToStandard.Add(ValueType.Int64, typeof(long)); ManagedToNative.Add(ValueType.UInt64, type_uint64()); NativeToManaged.Add(type_uint64(), ValueType.UInt64); + StandardToManaged.Add(typeof(ulong), ValueType.UInt64); + ManagedToStandard.Add(ValueType.UInt64, typeof(ulong)); ManagedToNative.Add(ValueType.Float, type_float()); NativeToManaged.Add(type_float(), ValueType.Float); + StandardToManaged.Add(typeof(float), ValueType.Float); + ManagedToStandard.Add(ValueType.Float, typeof(float)); ManagedToNative.Add(ValueType.Double, type_double()); NativeToManaged.Add(type_double(), ValueType.Double); + StandardToManaged.Add(typeof(double), ValueType.Double); + ManagedToStandard.Add(ValueType.Double, typeof(double)); ManagedToNative.Add(ValueType.String, type_string()); NativeToManaged.Add(type_string(), ValueType.String); + StandardToManaged.Add(typeof(string), ValueType.String); + ManagedToStandard.Add(ValueType.String, typeof(string)); ManagedToNative.Add(ValueType.Array, type_array()); NativeToManaged.Add(type_array(), ValueType.Array); @@ -804,9 +865,15 @@ static class ValueTypeBridge ManagedToNative.Add(ValueType.Error, type_error()); NativeToManaged.Add(type_error(), ValueType.Error); + StandardToManaged.Add(typeof(Eina.Error), ValueType.Error); + ManagedToStandard.Add(ValueType.Error, typeof(Eina.Error)); ManagedToNative.Add(ValueType.Object, type_object()); NativeToManaged.Add(type_object(), ValueType.Object); + // We don't use `typeof(Efl.Object)` directly in the StandartToManaged dictionary as typeof(myobj) may + // return a different type. For ManagedToStandard, we make use of C# generics covariance to create + // an collection of Efl.Objects when unwrapping. + ManagedToStandard.Add(ValueType.Object, typeof(Efl.Object)); ManagedToNative.Add(ValueType.Empty, IntPtr.Zero); NativeToManaged.Add(IntPtr.Zero, ValueType.Empty); @@ -994,7 +1061,29 @@ public class Value : IDisposable, IComparable, IEquatable } else { - throw new ArgumentException($"Unsupported type for direct construction: {objType}"); + // Container type conversion is supported only from IEnumerable + if (!obj.GetType().GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + { + throw new ArgumentException($"Unsupported type for direct construction: {objType}"); + } + + Type[] genericArguments = objType.GetGenericArguments(); + if (genericArguments.Count() != 1) + { + throw new ArgumentException($"Unsupported type for direct construction: {objType}"); + } + + var genericArg = genericArguments[0]; + + var argValueType = ValueTypeBridge.GetManaged(genericArg); + + Setup(ValueType.Array, argValueType); + + foreach (var item in obj as System.Collections.IEnumerable) + { + Append(item); + } + } } @@ -1553,6 +1642,32 @@ public class Value : IDisposable, IComparable, IEquatable Get(out o); return o; } + case ValueType.Array: + case ValueType.List: + { + // Eina Array and Lists will be unwrapped into a System.Collections.Generic.List + // usually to be handled as IEnumerable through LINQ. + var genericType = ValueTypeBridge.GetStandard(GetValueSubType()); + Type[] typeArgs = { genericType }; + var containerType = typeof(System.Collections.Generic.List<>); + var retType = containerType.MakeGenericType(typeArgs); + object ret = Activator.CreateInstance(retType); + + var addMeth = retType.GetMethod("Add"); + + if (addMeth == null) + { + throw new InvalidOperationException("Failed to get Add() method of container to wrap value"); + } + + for (int i = 0; i < Count(); i++) + { + object[] args = new object[]{ this[i] }; + addMeth.Invoke(ret, args); + } + + return ret; + } default: throw new InvalidOperationException($"Unsupported value type to unwrap: {GetValueType()}"); } diff --git a/src/tests/efl_mono/Value.cs b/src/tests/efl_mono/Value.cs index 1c83344675..ba56c36409 100644 --- a/src/tests/efl_mono/Value.cs +++ b/src/tests/efl_mono/Value.cs @@ -3,7 +3,9 @@ #pragma warning disable 1591 using System; +using System.Linq; using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; namespace TestSuite { @@ -1099,6 +1101,81 @@ public static class TestValueFromObject Test.AssertEquals(prop.GetValue(source), newObj); } } + + private class ComplexHolder + { + public IEnumerable Bag { get; set; } + public IEnumerable BagOfObjects { get; set; } + } + + public static void TestContainerFromToObject() + { + var initialBag = new Eina.Array(); + initialBag.Push(2); + initialBag.Push(4); + initialBag.Push(6); + + var source = new ComplexHolder { Bag = initialBag }; + var prop = source.GetType().GetProperty("Bag"); + var v = new Eina.Value(prop.GetValue(source)); + Test.AssertEquals(prop.GetValue(source), initialBag); + + Test.AssertEquals(v.GetValueType(), Eina.ValueType.Array); + Test.AssertEquals(v.GetValueSubType(), Eina.ValueType.Int32); + + Test.AssertEquals(v[0], initialBag[0]); + Test.AssertEquals(v[1], initialBag[1]); + Test.AssertEquals(v[2], initialBag[2]); + + v[0] = 100; + v[1] = 200; + v[2] = 300; + + prop.SetValue(source, v.Unwrap()); + + IEnumerable newVal = prop.GetValue(source) as IEnumerable; + var toCheck = newVal.ToList(); + + Test.AssertEquals(toCheck[0], 100); + Test.AssertEquals(toCheck[1], 200); + Test.AssertEquals(toCheck[2], 300); + } + + public static void TestObjectContainerFromToObject() + { + var initialBag = new Eina.Array(); + initialBag.Push(new Dummy.TestObject()); + initialBag.Push(new Dummy.TestObject()); + initialBag.Push(new Dummy.TestObject()); + + var source = new ComplexHolder { BagOfObjects = initialBag }; + var prop = source.GetType().GetProperty("BagOfObjects"); + var v = new Eina.Value(prop.GetValue(source)); + Test.AssertEquals(prop.GetValue(source), initialBag); + + Test.AssertEquals(v.GetValueType(), Eina.ValueType.Array); + Test.AssertEquals(v.GetValueSubType(), Eina.ValueType.Object); + + Test.AssertEquals(v[0], initialBag[0]); + Test.AssertEquals(v[1], initialBag[1]); + Test.AssertEquals(v[2], initialBag[2]); + + var first = new Dummy.TestObject(); + var second = new Dummy.TestObject(); + var third = new Dummy.TestObject(); + v[0] = first; + v[1] = second; + v[2] = third; + + prop.SetValue(source, v.Unwrap()); + + IEnumerable newVal = prop.GetValue(source) as IEnumerable; + var toCheck = newVal.ToList(); + + Test.AssertEquals(toCheck[0], first); + Test.AssertEquals(toCheck[1], second); + Test.AssertEquals(toCheck[2], third); + } } #pragma warning restore 1591 }