IGNITE-6896 .NET: support Multidimensional Arrays in binary serializer This closes #3031
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/fa52789d Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/fa52789d Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/fa52789d Branch: refs/heads/ignite-zk Commit: fa52789d7679d4c4b65efcdfb86cf146abb920fe Parents: f992386 Author: Pavel Tupitsyn <ptupit...@apache.org> Authored: Wed Nov 15 15:47:58 2017 +0300 Committer: Pavel Tupitsyn <ptupit...@apache.org> Committed: Wed Nov 15 15:47:58 2017 +0300 ---------------------------------------------------------------------- .../Binary/BinarySelfTest.cs | 88 +++++++++++++ .../Apache.Ignite.Core.csproj | 2 + .../Impl/Binary/BinaryReflectiveActions.cs | 16 +++ .../Impl/Binary/BinarySystemHandlers.cs | 13 +- .../Impl/Binary/BinaryUtils.cs | 41 +++++- .../Impl/Binary/Marshaller.cs | 1 + .../Impl/Binary/MultidimensionalArrayHolder.cs | 132 +++++++++++++++++++ .../Binary/MultidimensionalArraySerializer.cs | 48 +++++++ 8 files changed, 330 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/fa52789d/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs index 3ec1e8c..6ffce38 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs @@ -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"); + } + } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/fa52789d/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj index 165a57e..8a32583 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj @@ -116,6 +116,8 @@ <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" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/fa52789d/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs index 97964cf..a8ea9f1 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs @@ -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)) http://git-wip-us.apache.org/repos/asf/ignite/blob/fa52789d/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs index 3f16bc0..430b426 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs @@ -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)); } http://git-wip-us.apache.org/repos/asf/ignite/blob/fa52789d/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs index 20fea02..3123c07 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs @@ -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> http://git-wip-us.apache.org/repos/asf/ignite/blob/fa52789d/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs index 7212cd6..0a7b54c 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs @@ -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> http://git-wip-us.apache.org/repos/asf/ignite/blob/fa52789d/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArrayHolder.cs ---------------------------------------------------------------------- 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 index 0000000..f9f8df3 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArrayHolder.cs @@ -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); + } + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/fa52789d/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArraySerializer.cs ---------------------------------------------------------------------- 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 index 0000000..993b9d5 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/MultidimensionalArraySerializer.cs @@ -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; } + } + } +}