Repository: ignite
Updated Branches:
  refs/heads/master ec12824a5 -> 93bf555a9


IGNITE-6627 .NET: Fix serialization of enums within generic collections

* Fix EnumEqualityComparer serialization
* Fix enum arrays serialization
* Fix empty objects missing metadata

This closes #2864


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/93bf555a
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/93bf555a
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/93bf555a

Branch: refs/heads/master
Commit: 93bf555a98c472ff7028a641b32ef5d8ba8df7cd
Parents: ec12824
Author: Alexey Popov <tank2.a...@gmail.com>
Authored: Tue Oct 17 14:45:42 2017 +0300
Committer: Pavel Tupitsyn <ptupit...@apache.org>
Committed: Tue Oct 17 14:45:42 2017 +0300

----------------------------------------------------------------------
 .../Apache.Ignite.Core.Tests.csproj             |   2 +
 .../Serializable/GenericCollectionsTest.cs      | 112 +++++++++++++++++++
 .../Client/Cache/CacheTest.cs                   |  76 +++++++++++++
 .../Client/Cache/EmptyObject.cs                 |  54 +++++++++
 .../Impl/Binary/BinarySystemHandlers.cs         |  16 +--
 .../Impl/Binary/BinaryWriter.cs                 |   7 ++
 .../Impl/Binary/SerializableSerializer.cs       |  11 +-
 .../Binary/Structure/BinaryStructureTracker.cs  |  12 +-
 8 files changed, 274 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
index ec85ca2..7ec75af 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
@@ -78,6 +78,7 @@
     <Compile Include="Binary\BinaryReaderWriterTest.cs" />
     <Compile Include="Binary\BinarySelfTestSimpleName.cs" />
     <Compile Include="Binary\EnumsTestOnline.cs" />
+    <Compile Include="Binary\Serializable\GenericCollectionsTest.cs" />
     <Compile Include="Cache\PersistentStoreTest.cs" />
     <Compile Include="Cache\Query\Linq\CacheLinqTest.CompiledQuery.cs" />
     <Compile Include="Cache\Query\Linq\CacheLinqTest.DateTime.cs" />
@@ -94,6 +95,7 @@
     <Compile Include="Cache\Store\CacheStoreSessionTestSharedFactory.cs" />
     <Compile Include="Client\Cache\CacheTest.cs" />
     <Compile Include="Client\Cache\CacheTestNoMeta.cs" />
+    <Compile Include="Client\Cache\EmptyObject.cs" />
     <Compile Include="Client\Cache\ScanQueryTest.cs" />
     <Compile Include="Client\Cache\Person.cs" />
     <Compile Include="Client\ClientTestBase.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/GenericCollectionsTest.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/GenericCollectionsTest.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/GenericCollectionsTest.cs
new file mode 100644
index 0000000..cfbe824d
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/GenericCollectionsTest.cs
@@ -0,0 +1,112 @@
+/*
+ * 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.Tests.Binary.Serializable
+{
+    using System.Collections.Generic;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests Generic collections serializtion/deserialization scenarios.
+    /// </summary>
+    public class GenericCollectionsTest
+    {
+        /// <summary>
+        /// Tests Dictionary.
+        /// </summary>
+        [Test]
+        public void TestDictionary()
+        {
+            TestCollection(new Dictionary<int, int> {{1, 1}, {2, 2}});
+            TestCollection(new Dictionary<ByteEnum, int> {{ByteEnum.One, 1}, 
{ByteEnum.Two, 2}});
+            TestCollection(new Dictionary<IntEnum, int> {{IntEnum.One, 1}, 
{IntEnum.Two, 2}});
+        }
+
+        /// <summary>
+        /// Tests SortedDictionary.
+        /// </summary>
+        [Test]
+        public void TestSortedDictionary()
+        {
+            TestCollection(new SortedDictionary<int, int> {{1, 1}, {2, 2}});
+            TestCollection(new SortedDictionary<ByteEnum, int> {{ByteEnum.One, 
1}, {ByteEnum.Two, 2}});
+            TestCollection(new SortedDictionary<IntEnum, int> {{IntEnum.One, 
1}, {IntEnum.Two, 2}});
+        }
+
+        /// <summary>
+        /// Tests List.
+        /// </summary>
+        [Test]
+        public void TestList()
+        {
+            TestCollection(new List<int> {1, 2});
+            TestCollection(new List<ByteEnum> {ByteEnum.One, ByteEnum.Two});
+            TestCollection(new List<IntEnum> {IntEnum.One, IntEnum.Two});
+        }
+
+        /// <summary>
+        /// Tests LinkedList.
+        /// </summary>
+        [Test]
+        public void TestLinkedList()
+        {
+            TestCollection(new LinkedList<int>(new List<int> { 1, 2 }));
+            TestCollection(new LinkedList<ByteEnum>(new List<ByteEnum> 
{ByteEnum.One, ByteEnum.Two}));
+            TestCollection(new LinkedList<IntEnum>(new List<IntEnum> 
{IntEnum.One, IntEnum.Two}));
+        }
+
+        /// <summary>
+        /// Tests HashSet.
+        /// </summary>
+        [Test]
+        public void TestHashSet()
+        {
+            TestCollection(new HashSet<int> {1, 2});
+            TestCollection(new HashSet<ByteEnum> {ByteEnum.One, ByteEnum.Two});
+            TestCollection(new HashSet<IntEnum> {IntEnum.One, IntEnum.Two});
+        }
+
+        /// <summary>
+        /// Tests SortedSet.
+        /// </summary>
+        [Test]
+        public void TestSortedSet()
+        {
+            TestCollection(new SortedSet<int> {1, 2});
+            TestCollection(new SortedSet<ByteEnum> {ByteEnum.One, 
ByteEnum.Two});
+            TestCollection(new SortedSet<IntEnum> {IntEnum.One, IntEnum.Two});
+        }
+
+        private static void TestCollection<T>(ICollection<T> collection)
+        {
+            var res = TestUtils.SerializeDeserialize(collection);
+            Assert.AreEqual(collection, res);
+        }
+
+        private enum ByteEnum : byte
+        {
+            One = 1,
+            Two = 2,
+        }
+
+        private enum IntEnum 
+        {
+            One = 1,
+            Two = 2,
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs
index 083038a..f2dd1de 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs
@@ -68,6 +68,22 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
         }
 
         /// <summary>
+        /// Tests the cache put / get for Empty object type.
+        /// </summary>
+        [Test]
+        public void TestPutGetEmptyObject()
+        {
+            using (var client = GetClient())
+            {
+                var serverCache = GetCache<EmptyObject>();
+                var clientCache = client.GetCache<int, EmptyObject>(CacheName);
+
+                serverCache.Put(1, new EmptyObject());
+                Assert.IsNotNull(clientCache.Get(1));
+            }
+        }
+
+        /// <summary>
         /// Tests the cache put / get with user data types.
         /// </summary>
         [Test]
@@ -116,6 +132,60 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
         }
 
         /// <summary>
+        /// Tests the cache put / get for Dictionary with Enum keys.
+        /// </summary>
+        [Test]
+        public void TestPutGetDictionary([Values(true, false)] bool 
compactFooter)
+        {
+            var cfg = GetClientConfiguration();
+
+            cfg.BinaryConfiguration = new BinaryConfiguration
+            {
+                CompactFooter = compactFooter
+            };
+
+            using (var client = Ignition.StartClient(cfg))
+            {
+                var dict = new Dictionary<ByteEnum, int> { { ByteEnum.One, 1 
}, { ByteEnum.Two, 2 } };
+
+                var serverCache = GetCache<Dictionary<ByteEnum, int>>();
+                var clientCache = client.GetCache<int, Dictionary<ByteEnum, 
int>>(CacheName);
+
+                serverCache.Put(1, dict);
+                var res = clientCache.Get(1);
+
+                Assert.AreEqual(dict, res);
+            }
+        }
+
+        /// <summary>
+        /// Tests the cache put / get for HashSet with Enum keys.
+        /// </summary>
+        [Test]
+        public void TestPutGetHashSet([Values(true, false)] bool compactFooter)
+        {
+            var cfg = GetClientConfiguration();
+
+            cfg.BinaryConfiguration = new BinaryConfiguration
+            {
+                CompactFooter = compactFooter
+            };
+
+            using (var client = Ignition.StartClient(cfg))
+            {
+                var hashSet = new HashSet<ByteEnum> { ByteEnum.One, 
ByteEnum.Two };
+
+                var serverCache = GetCache<HashSet<ByteEnum>>();
+                var clientCache = client.GetCache<int, 
HashSet<ByteEnum>>(CacheName);
+
+                serverCache.Put(1, hashSet);
+                var res = clientCache.Get(1);
+
+                Assert.AreEqual(hashSet, res);
+            }
+        }
+
+        /// <summary>
         /// Tests the TryGet method.
         /// </summary>
         [Test]
@@ -779,5 +849,11 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
         {
             public Container Inner;
         }
+
+        public enum ByteEnum : byte
+        {
+            One = 1,
+            Two = 2,
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/EmptyObject.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/EmptyObject.cs 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/EmptyObject.cs
new file mode 100644
index 0000000..47db939
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/EmptyObject.cs
@@ -0,0 +1,54 @@
+/*
+ * 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.Tests.Client.Cache
+{
+    using System;
+    using System.Runtime.Serialization;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Object with no fields.
+    /// </summary>
+    [Serializable]
+    public class EmptyObject : ISerializable
+    {
+        /// <summary>
+        /// Initializes a new instance of the EmptyObject class.
+        /// </summary>
+        public EmptyObject()
+        {
+            // No-op.
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the EmptyObject class.
+        /// </summary>
+        private EmptyObject(SerializationInfo info, StreamingContext context)
+        {
+            Assert.AreEqual(StreamingContextStates.All, context.State);
+            Assert.IsNull(context.Context);
+        }
+
+        /** <inheritdoc /> */
+        public void GetObjectData(SerializationInfo info, StreamingContext 
context)
+        {
+            Assert.AreEqual(StreamingContextStates.All, context.State);
+            Assert.IsNull(context.Context);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/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 f55a11f..3f16bc0 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
@@ -110,9 +110,9 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             // 13. Arbitrary dictionary.
             ReadHandlers[BinaryTypeId.Dictionary] = new 
BinarySystemReader(ReadDictionary);
-            
-            // 14. Enum.
-            ReadHandlers[BinaryTypeId.ArrayEnum] = new 
BinarySystemReader(ReadEnumArray);
+
+            // 14. Enum. Should be read as Array, see WriteEnumArray 
implementation.
+            ReadHandlers[BinaryTypeId.ArrayEnum] = new 
BinarySystemReader(ReadArray);
         }
 
         /// <summary>
@@ -473,16 +473,6 @@ namespace Apache.Ignite.Core.Impl.Binary
             ctx.WriteInt(binEnum.EnumValue);
         }
 
-        /**
-         * <summary>Read enum array.</summary>
-         */
-        private static object ReadEnumArray(BinaryReader ctx, Type type)
-        {
-            var elemType = type.GetElementType() ?? typeof(object);
-
-            return BinaryUtils.ReadTypedArray(ctx, true, elemType);
-        }
-
         /// <summary>
         /// Reads the array.
         /// </summary>

http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
index f59f17c..b98ad5f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
@@ -1493,6 +1493,13 @@ namespace Apache.Ignite.Core.Impl.Binary
         {
             Debug.Assert(desc != null);
 
+            if (!desc.UserType && (fields == null || fields.Count == 0))
+            {
+                // System types with no fields (most of them) do not need to 
be sent.
+                // AffinityKey is an example of system type with metadata.
+                return;
+            }
+
             if (_metas == null)
             {
                 _metas = new Dictionary<int, BinaryType>(1)

http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs
index e660cff..80f267a 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs
@@ -304,9 +304,16 @@ namespace Apache.Ignite.Core.Impl.Binary
             {
                 return new TypeResolver().ResolveType(serInfo.FullTypeName, 
serInfo.AssemblyName);
             }
-            
-            if (serInfo.ObjectType != serializable.GetType())
+
+            if (serInfo.ObjectType != serializable.GetType() &&
+                typeof(ISerializable).IsAssignableFrom(serInfo.ObjectType))
             {
+                // serInfo.ObjectType should be ISerializable. There is a 
known case for generic collections:
+                // serializable is EnumEqualityComparer : ISerializable 
+                // and serInfo.ObjectType is ObjectEqualityComparer (does not 
implement ISerializable interface).
+                // Please read a possible explanation here:
+                // 
http://dotnetstudio.blogspot.ru/2012/06/net-35-to-net-40-enum.html
+
                 return serInfo.ObjectType;
             }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs
index 8f44e00..3517342 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs
@@ -110,11 +110,21 @@ namespace Apache.Ignite.Core.Impl.Binary.Structure
 
                     var fields = metaHnd.OnObjectWriteFinished();
 
-                    // A new schema may be added, but no new fields. 
+                    // A new schema may be added, but no new fields.
                     // In this case, we should still call SaveMetadata even if 
fields are null
                     writer.SaveMetadata(_desc, fields);
                 }
             }
+            else
+            {
+                // Special case when the object is with no properties.
+                // Save meta to Marshaller.
+                writer.Marshaller.GetBinaryTypeHandler(_desc);
+
+                // Save meta to cluster.
+                writer.SaveMetadata(_desc, null);
+                return;
+            }
         }
 
         /// <summary>

Reply via email to