IGNITE-6896 .NET: support Multidimensional Arrays in binary serializer
authorPavel Tupitsyn <ptupitsyn@apache.org>
Wed, 15 Nov 2017 12:47:58 +0000 (15:47 +0300)
committerPavel Tupitsyn <ptupitsyn@apache.org>
Wed, 15 Nov 2017 12:47:58 +0000 (15:47 +0300)
This closes #3031

modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArrayHolder.cs [new file with mode: 0644]
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArraySerializer.cs [new file with mode: 0644]

index 3ec1e8c..6ffce38 100644 (file)
@@ -1532,6 +1532,58 @@ namespace Apache.Ignite.Core.Tests.Binary
         }
 
         /// <summary>
+        /// Tests the jagged arrays.
+        /// </summary>
+        [Test]
+        public void TestJaggedArrays()
+        {
+            int[][] ints = {new[] {1, 2, 3}, new[] {4, 5, 6}};
+            Assert.AreEqual(ints, TestUtils.SerializeDeserialize(ints));
+
+            uint[][][] uints = {new[] {new uint[] {1, 2, 3}, new uint[] {4, 5}}};
+            Assert.AreEqual(uints, TestUtils.SerializeDeserialize(uints));
+
+            PropertyType[][][] objs = {new[] {new[] {new PropertyType {Field1 = 42}}}};
+            Assert.AreEqual(42, TestUtils.SerializeDeserialize(objs)[0][0][0].Field1);
+
+            var obj = new MultidimArrays { JaggedInt = ints, JaggedUInt = uints };
+            var resObj = TestUtils.SerializeDeserialize(obj);
+            Assert.AreEqual(obj.JaggedInt, resObj.JaggedInt);
+            Assert.AreEqual(obj.JaggedUInt, resObj.JaggedUInt);
+
+            var obj2 = new MultidimArraysBinarizable { JaggedInt = ints, JaggedUInt = uints };
+            var resObj2 = TestUtils.SerializeDeserialize(obj);
+            Assert.AreEqual(obj2.JaggedInt, resObj2.JaggedInt);
+            Assert.AreEqual(obj2.JaggedUInt, resObj2.JaggedUInt);
+        }
+
+        /// <summary>
+        /// Tests the multidimensional arrays.
+        /// </summary>
+        [Test]
+        public void TestMultidimensionalArrays()
+        {
+            int[,] ints = {{1, 2, 3}, {4, 5, 6}};
+            Assert.AreEqual(ints, TestUtils.SerializeDeserialize(ints));
+
+            uint[,,] uints = {{{1, 2, 3}, {4, 5, 6}}, {{7, 8, 9}, {10, 11, 12}}};
+            Assert.AreEqual(uints, TestUtils.SerializeDeserialize(uints));
+
+            PropertyType[,] objs = {{new PropertyType {Field1 = 123}}};
+            Assert.AreEqual(123, TestUtils.SerializeDeserialize(objs)[0, 0].Field1);
+            
+            var obj = new MultidimArrays { MultidimInt = ints, MultidimUInt = uints };
+            var resObj = TestUtils.SerializeDeserialize(obj);
+            Assert.AreEqual(obj.MultidimInt, resObj.MultidimInt);
+            Assert.AreEqual(obj.MultidimUInt, resObj.MultidimUInt);
+
+            var obj2 = new MultidimArraysBinarizable { MultidimInt = ints, MultidimUInt = uints };
+            var resObj2 = TestUtils.SerializeDeserialize(obj);
+            Assert.AreEqual(obj2.MultidimInt, resObj2.MultidimInt);
+            Assert.AreEqual(obj2.MultidimUInt, resObj2.MultidimUInt);
+        }
+
+        /// <summary>
         /// Tests the compact footer setting.
         /// </summary>
         [Test]
@@ -2604,5 +2656,41 @@ namespace Apache.Ignite.Core.Tests.Binary
                 return !left.Equals(right);
             }
         }
+
+        private class MultidimArrays
+        {
+            public int[][] JaggedInt { get; set; }
+            public uint[][][] JaggedUInt { get; set; }
+
+            public int[,] MultidimInt { get; set; }
+            public uint[,,] MultidimUInt { get; set; }
+        }
+
+        private class MultidimArraysBinarizable : IBinarizable
+        {
+            public int[][] JaggedInt { get; set; }
+            public uint[][][] JaggedUInt { get; set; }
+
+            public int[,] MultidimInt { get; set; }
+            public uint[,,] MultidimUInt { get; set; }
+            
+            public void WriteBinary(IBinaryWriter writer)
+            {
+                writer.WriteObject("JaggedInt", JaggedInt);
+                writer.WriteObject("JaggedUInt", JaggedUInt);
+
+                writer.WriteObject("MultidimInt", MultidimInt);
+                writer.WriteObject("MultidimUInt", MultidimUInt);
+            }
+
+            public void ReadBinary(IBinaryReader reader)
+            {
+                JaggedInt = reader.ReadObject<int[][]>("JaggedInt");
+                JaggedUInt = reader.ReadObject<uint[][][]>("JaggedUInt");
+
+                MultidimInt = reader.ReadObject<int[,]>("MultidimInt");
+                MultidimUInt = reader.ReadObject<uint[,,]>("MultidimUInt");
+            }
+        }
     }
 }
index 165a57e..8a32583 100644 (file)
     <Compile Include="Configuration\WalMode.cs" />
     <Compile Include="Impl\Binary\BinaryTypeId.cs" />
     <Compile Include="Impl\Binary\IBinaryRawWriteAware.cs" />
+    <Compile Include="Impl\Binary\MultidimensionalArrayHolder.cs" />
+    <Compile Include="Impl\Binary\MultidimensionalArraySerializer.cs" />
     <Compile Include="Impl\Client\Cache\CacheFlags.cs" />
     <Compile Include="Impl\Client\Cache\Query\ClientQueryCursor.cs" />
     <Compile Include="Impl\Cache\Query\PlatformQueryQursorBase.cs" />
index 97964cf..a8ea9f1 100644 (file)
@@ -121,7 +121,9 @@ namespace Apache.Ignite.Core.Impl.Binary
             if (type.IsPrimitive)
                 HandlePrimitive(field, out writeAction, out readAction, raw);
             else if (type.IsArray)
+            {
                 HandleArray(field, out writeAction, out readAction, raw);
+            }
             else
                 HandleOther(field, out writeAction, out readAction, raw, forceTimestamp);
         }
@@ -252,6 +254,20 @@ namespace Apache.Ignite.Core.Impl.Binary
         private static void HandleArray(FieldInfo field, out BinaryReflectiveWriteAction writeAction,
             out BinaryReflectiveReadAction readAction, bool raw)
         {
+            if (field.FieldType.GetArrayRank() > 1)
+            {
+                writeAction = raw
+                    ? GetRawWriter<Array>(field, (w, o) => w.WriteObject(
+                        o == null ? null : new MultidimensionalArrayHolder(o)))
+                    : GetWriter<Array>(field, (f, w, o) => w.WriteObject(f,
+                        o == null ? null : new MultidimensionalArrayHolder(o)));
+                readAction = raw
+                    ? GetRawReader(field, r => r.ReadObject<object>())
+                    : GetReader(field, (f, r) => r.ReadObject<object>(f));
+
+                return;
+            }
+
             Type elemType = field.FieldType.GetElementType();
 
             if (elemType == typeof (bool))
index 3f16bc0..430b426 100644 (file)
@@ -178,6 +178,13 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             if (type.IsArray)
             {
+                if (type.GetArrayRank() > 1)
+                {
+                    // int[,]-style arrays are wrapped, see comments in holder.
+                    return new BinarySystemWriteHandler<Array>(
+                        (w, o) => w.WriteObject(new MultidimensionalArrayHolder(o)), true);
+                }
+
                 // We know how to write any array type.
                 Type elemType = type.GetElementType();
                 
@@ -491,11 +498,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                 {
                     // Infer element type from typeId.
                     var typeId = ctx.ReadInt();
-
-                    if (typeId != BinaryUtils.ObjTypeId)
-                    {
-                        elemType = ctx.Marshaller.GetDescriptor(true, typeId, true).Type;
-                    }
+                    elemType = BinaryUtils.GetArrayElementType(typeId, ctx.Marshaller);
 
                     return BinaryUtils.ReadTypedArray(ctx, false, elemType ?? typeof(object));
                 }
index 20fea02..3123c07 100644 (file)
@@ -1024,6 +1024,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         public static void WriteArray(Array val, BinaryWriter ctx, int? elemTypeId = null)
         {
             Debug.Assert(val != null && ctx != null);
+            Debug.Assert(val.Rank == 1);
 
             IBinaryStream stream = ctx.Stream;
 
@@ -1034,18 +1035,15 @@ namespace Apache.Ignite.Core.Impl.Binary
             else
             {
                 var elemType = val.GetType().GetElementType();
-
                 Debug.Assert(elemType != null);
 
-                var typeId = ObjTypeId;
-
-                if (elemType != typeof(object))
-                    typeId = ctx.Marshaller.GetDescriptor(elemType).TypeId;
-
+                var typeId = GetArrayElementTypeId(val, ctx.Marshaller);
                 stream.WriteInt(typeId);
 
                 if (typeId == BinaryTypeId.Unregistered)
+                {
                     ctx.WriteString(elemType.FullName);
+                }
             }
 
             stream.WriteInt(val.Length);
@@ -1055,6 +1053,37 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
+        /// Gets the array element type identifier.
+        /// </summary>
+        public static int GetArrayElementTypeId(Array val, Marshaller marsh)
+        {
+            var elemType = val.GetType().GetElementType();
+            Debug.Assert(elemType != null);
+
+            return GetArrayElementTypeId(elemType, marsh);
+        }
+
+        /// <summary>
+        /// Gets the array element type identifier.
+        /// </summary>
+        public static int GetArrayElementTypeId(Type elemType, Marshaller marsh)
+        {
+            return elemType == typeof(object) 
+                ? ObjTypeId 
+                : marsh.GetDescriptor(elemType).TypeId;
+        }
+
+        /// <summary>
+        /// Gets the type of the array element.
+        /// </summary>
+        public static Type GetArrayElementType(int typeId, Marshaller marsh)
+        {
+            return typeId == ObjTypeId
+                ? typeof(object)
+                : marsh.GetDescriptor(true, typeId, true).Type;
+        }
+
+        /// <summary>
         /// Read array.
         /// </summary>
         /// <param name="ctx">Read context.</param>
index 7212cd6..0a7b54c 100644 (file)
@@ -725,6 +725,7 @@ namespace Apache.Ignite.Core.Impl.Binary
             AddSystemType(0, r => new AssemblyRequest(r));
             AddSystemType(0, r => new AssemblyRequestResult(r));
             AddSystemType<PeerLoadingObjectHolder>(0, null, serializer: new PeerLoadingObjectHolderSerializer());
+            AddSystemType<MultidimensionalArrayHolder>(0, null, serializer: new MultidimensionalArraySerializer());
         }
 
         /// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArrayHolder.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArrayHolder.cs
new file mode 100644 (file)
index 0000000..f9f8df3
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Impl.Binary
+{
+    using System;
+    using System.Collections;
+    using System.Diagnostics;
+    using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Impl.Deployment;
+
+    /// <summary>
+    /// Wrapper for multidimensional arrays (int[,] -style).
+    /// <para />
+    /// Jagged arrays (int[][]) are fully supported by the engine and are interoperable with Java.
+    /// However, there is no int[,]-style arrays in Java, and there is no way to support them in a generic way.
+    /// So we have to wrap them inside an object (so it looks like a BinaryObject in Java).
+    /// </summary>
+    internal sealed class MultidimensionalArrayHolder : IBinaryWriteAware
+    {
+        /** Object. */
+        private readonly Array _array;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="PeerLoadingObjectHolder"/> class.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        public MultidimensionalArrayHolder(Array o)
+        {
+            Debug.Assert(o != null);
+            Debug.Assert(o.Rank > 1);
+
+            _array = o;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MultidimensionalArrayHolder"/> class.
+        /// </summary>
+        /// <param name="reader">The reader.</param>
+        public MultidimensionalArrayHolder(BinaryReader reader)
+        {
+            var typeId = reader.ReadInt();
+            var type = BinaryUtils.GetArrayElementType(typeId, reader.Marshaller);
+
+            var rank = reader.ReadInt();
+            var lengths = new int[rank];
+            var totalLen = 1;
+
+            for (var i = 0; i < rank; i++)
+            {
+                var len = reader.ReadInt();
+                lengths[i] = len;
+                totalLen *= len;
+            }
+
+            _array = System.Array.CreateInstance(type, lengths);
+
+            for (var i = 0; i < totalLen; i++)
+            {
+                var obj = reader.ReadObject<object>();
+                var idx = GetIndices(i, lengths);
+
+                _array.SetValue(Convert.ChangeType(obj, type), idx);
+            }
+        }
+
+        /// <summary>
+        /// Gets the indices in a multidimensional array from a global index.
+        /// </summary>
+        private static int[] GetIndices(int globalIdx, int[] lengths)
+        {
+            var res = new int[lengths.Length];
+
+            for (var i = lengths.Length - 1; i >= 0; i--)
+            {
+                var len = lengths[i];
+
+                res[i] = globalIdx % len;
+                globalIdx = globalIdx / len;
+            }
+
+            return res;
+        }
+
+        /// <summary>
+        /// Gets the object.
+        /// </summary>
+        public object Array
+        {
+            get { return _array; }
+        }
+
+        /** <inheritdoc /> */
+        public void WriteBinary(IBinaryWriter writer)
+        {
+            var raw = writer.GetRawWriter();
+
+            // Array type.
+            raw.WriteInt(BinaryUtils.GetArrayElementTypeId(_array, ((BinaryWriter) writer).Marshaller));
+
+            // Number of dimensions.
+            var rank = _array.Rank;
+            raw.WriteInt(rank);
+
+            // Sizes per dimensions.
+            for (var i = 0; i < rank; i++)
+            {
+                raw.WriteInt(_array.GetLength(i));
+            }
+
+            // Data.
+            foreach (var obj in (IEnumerable)_array)
+            {
+                raw.WriteObject(obj);
+            }
+        }
+    }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArraySerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArraySerializer.cs
new file mode 100644 (file)
index 0000000..993b9d5
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Impl.Binary
+{
+    using System;
+    using Apache.Ignite.Core.Impl.Common;
+
+    /// <summary>
+    /// Serializer for <see cref="MultidimensionalArrayHolder"/>. Unwraps underlying object automatically.
+    /// </summary>
+    internal sealed class MultidimensionalArraySerializer : IBinarySerializerInternal
+    {
+        /** <inheritdoc /> */
+        public void WriteBinary<T>(T obj, BinaryWriter writer)
+        {
+            TypeCaster<MultidimensionalArrayHolder>.Cast(obj).WriteBinary(writer);
+        }
+
+        /** <inheritdoc /> */
+        public T ReadBinary<T>(BinaryReader reader, IBinaryTypeDescriptor desc, int pos, Type typeOverride)
+        {
+            var holder = new MultidimensionalArrayHolder(reader);
+
+            return (T) holder.Array;
+        }
+
+        /** <inheritdoc /> */
+        public bool SupportsHandles
+        {
+            get { return true; }
+        }
+    }
+}