IGNITE-5358 .NET: Fix nullable enum field handling in binary objects This closes #2048
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/34409fd5 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/34409fd5 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/34409fd5 Branch: refs/heads/ignite-5075 Commit: 34409fd59374a8fad31c592a5517e8d80a032cff Parents: c7638bc Author: Pavel Tupitsyn <[email protected]> Authored: Fri Jun 2 12:53:57 2017 +0300 Committer: Pavel Tupitsyn <[email protected]> Committed: Fri Jun 2 12:53:57 2017 +0300 ---------------------------------------------------------------------- .../Apache.Ignite.Core.Tests.csproj | 1 + .../Binary/BinarySelfTest.cs | 2 +- .../Binary/EnumsTest.cs | 299 ++++++++++++++++++- .../Binary/EnumsTestOnline.cs | 45 +++ .../Impl/Binary/BinaryReader.cs | 9 +- .../Impl/Binary/BinaryReflectiveActions.cs | 11 +- .../Impl/Binary/BinaryUtils.cs | 2 + .../Impl/Binary/BinaryWriter.cs | 4 +- 8 files changed, 348 insertions(+), 25 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/34409fd5/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 6d4f34b..d459d2a 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 @@ -75,6 +75,7 @@ <Compile Include="Binary\BinaryNameMapperTest.cs" /> <Compile Include="Binary\BinaryReaderWriterTest.cs" /> <Compile Include="Binary\BinarySelfTestSimpleName.cs" /> + <Compile Include="Binary\EnumsTestOnline.cs" /> <Compile Include="Deployment\GetAddressFunc.cs" /> <Compile Include="Deployment\PeerAssemblyLoadingAllApisTest.cs" /> <Compile Include="Deployment\PeerAssemblyLoadingVersioningTest.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/34409fd5/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 bae126f..2b22d5a 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs @@ -593,7 +593,7 @@ namespace Apache.Ignite.Core.Tests.Binary Assert.AreEqual(marsh.Unmarshal<TestEnum>(data), val); - var binEnum = marsh.Unmarshal<IBinaryObject>(data, true); + var binEnum = marsh.Unmarshal<IBinaryObject>(data, BinaryMode.ForceBinary); Assert.AreEqual(val, (TestEnum) binEnum.EnumValue); } http://git-wip-us.apache.org/repos/asf/ignite/blob/34409fd5/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/EnumsTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/EnumsTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/EnumsTest.cs index ab54f16..18ef29a 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/EnumsTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/EnumsTest.cs @@ -20,6 +20,7 @@ namespace Apache.Ignite.Core.Tests.Binary using System; using System.Runtime.Serialization; using Apache.Ignite.Core.Binary; + using Apache.Ignite.Core.Impl; using Apache.Ignite.Core.Impl.Binary; using Apache.Ignite.Core.Impl.Common; using NUnit.Framework; @@ -65,7 +66,9 @@ namespace Apache.Ignite.Core.Tests.Binary /// </summary> private static void CheckValue<T>(T val, bool isBinaryEnum = true) { - var marsh = new Marshaller(null) {CompactFooter = false}; + var marsh = GetMarshaller(); + var ignite = Ignition.TryGetIgnite(); + var bytes = marsh.Marshal(val); var res = marsh.Unmarshal<T>(bytes); var binRes = marsh.Unmarshal<IBinaryObject>(bytes, BinaryMode.ForceBinary); @@ -76,8 +79,17 @@ namespace Apache.Ignite.Core.Tests.Binary if (isBinaryEnum) { Assert.AreEqual(TypeCaster<int>.Cast(val), binRes.EnumValue); - Assert.AreEqual(string.Format("BinaryEnum [typeId={0}, enumValue={1}]", - BinaryUtils.GetStringHashCode(typeof(T).FullName), binRes.EnumValue), binRes.ToString()); + + if (ignite != null) + { + Assert.AreEqual(string.Format("{0} [typeId={1}, enumValue={2}, enumValueName={3}]", + typeof(T).FullName, binRes.GetBinaryType().TypeId, binRes.EnumValue, val), binRes.ToString()); + } + else + { + Assert.AreEqual(string.Format("BinaryEnum [typeId={0}, enumValue={1}]", + BinaryUtils.GetStringHashCode(typeof(T).FullName), binRes.EnumValue), binRes.ToString()); + } } else { @@ -85,10 +97,73 @@ namespace Apache.Ignite.Core.Tests.Binary } // Check array. - var arr = new[] {val, val}; - var arrRes = TestUtils.SerializeDeserialize(arr); + CheckSerializeDeserialize(new[] {val, val}); + + // Check caching. + if (ignite != null) + { + var cache = ignite.GetOrCreateCache<int, T>(typeof(T).FullName); + var binCache = cache.WithKeepBinary<int, IBinaryObject>(); + + cache[1] = val; + res = cache[1]; + binRes = binCache[1]; + + Assert.AreEqual(val, res); + Assert.AreEqual(val, binRes.Deserialize<T>()); + + var arrCache = ignite.GetOrCreateCache<int, T[]>(typeof(T[]).FullName); + arrCache[1] = new[] {val, val}; + Assert.AreEqual(new[] { val, val }, arrCache[1]); + } + } + + /// <summary> + /// Gets the marshaller. + /// </summary> + private static Marshaller GetMarshaller() + { + var ignite = Ignition.TryGetIgnite(); + + return ignite != null + ? ((Ignite) ignite).Marshaller + : new Marshaller(null) {CompactFooter = false}; + } + + /// <summary> + /// Serializes and deserializes a value. + /// </summary> + private static void CheckSerializeDeserialize<T>(T val, bool cacheOnly = false) + { + if (!cacheOnly) + { + var marsh = GetMarshaller(); + + var res = marsh.Unmarshal<T>(marsh.Marshal(val)); + Assert.AreEqual(val, res); + } + + var ignite = Ignition.TryGetIgnite(); + + if (ignite != null) + { + var cache = ignite.GetOrCreateCache<int, T>(typeof(T).FullName); + + cache.Put(1, val); + var res = cache.Get(1); - Assert.AreEqual(arr, arrRes); + Assert.AreEqual(val, res); + } + } + + /// <summary> + /// Convert to IBinaryObject. + /// </summary> + private static IBinaryObject ToBinary<T>(T val) + { + var marsh = GetMarshaller(); + + return marsh.Unmarshal<IBinaryObject>(marsh.Marshal(val), BinaryMode.ForceBinary); } /// <summary> @@ -100,8 +175,7 @@ namespace Apache.Ignite.Core.Tests.Binary // Min values. var val = new EnumsBinarizable(); - var res = TestUtils.SerializeDeserialize(val); - Assert.AreEqual(val, res); + CheckSerializeDeserialize(val); // Max values. val = new EnumsBinarizable @@ -116,8 +190,61 @@ namespace Apache.Ignite.Core.Tests.Binary UShort = UShortEnum.Bar }; - res = TestUtils.SerializeDeserialize(val); - Assert.AreEqual(val, res); + CheckSerializeDeserialize(val); + } + + /// <summary> + /// Tests enums as a BinaryObject field in binarizable object. + /// </summary> + [Test] + public void TestBinarizableFieldAsBinaryObject() + { + // Null values. + var val = new EnumsBinaryForm(); + + CheckSerializeDeserialize(val); + + // Max values. + val = new EnumsBinaryForm + { + Byte = ToBinary(ByteEnum.Bar), + Int = ToBinary(IntEnum.Bar), + Long = ToBinary(LongEnum.Bar), + SByte = ToBinary(SByteEnum.Bar), + Short = ToBinary(ShortEnum.Bar), + UInt = ToBinary(UIntEnum.Bar), + ULong = ToBinary(ULongEnum.Bar), + UShort = ToBinary(UShortEnum.Bar) + }; + + CheckSerializeDeserialize(val, true); + } + + /// <summary> + /// Tests enums as a nullable field in binarizable object. + /// </summary> + [Test] + public void TestBinarizableNullableField() + { + // Default values. + var val = new EnumsBinarizableNullable(); + + CheckSerializeDeserialize(val); + + // Max values. + val = new EnumsBinarizableNullable + { + Byte = ByteEnum.Bar, + Int = IntEnum.Bar, + Long = LongEnum.Bar, + SByte = SByteEnum.Bar, + Short = ShortEnum.Bar, + UInt = UIntEnum.Bar, + ULong = ULongEnum.Bar, + UShort = UShortEnum.Bar + }; + + CheckSerializeDeserialize(val); } /// <summary> @@ -129,8 +256,7 @@ namespace Apache.Ignite.Core.Tests.Binary // Min values. var val = new EnumsSerializable(); - var res = TestUtils.SerializeDeserialize(val); - Assert.AreEqual(val, res); + CheckSerializeDeserialize(val); // Max values. val = new EnumsSerializable @@ -145,8 +271,34 @@ namespace Apache.Ignite.Core.Tests.Binary UShort = UShortEnum.Bar }; - res = TestUtils.SerializeDeserialize(val); - Assert.AreEqual(val, res); + CheckSerializeDeserialize(val); + } + + /// <summary> + /// Tests enums as a nullable field in ISerializable object. + /// </summary> + [Test] + public void TestSerializableNullableField() + { + // Default values. + var val = new EnumsSerializableNullable(); + + CheckSerializeDeserialize(val); + + // Max values. + val = new EnumsSerializableNullable + { + Byte = ByteEnum.Bar, + Int = IntEnum.Bar, + Long = LongEnum.Bar, + SByte = SByteEnum.Bar, + Short = ShortEnum.Bar, + UInt = UIntEnum.Bar, + ULong = ULongEnum.Bar, + UShort = UShortEnum.Bar + }; + + CheckSerializeDeserialize(val); } private enum ByteEnum : byte @@ -240,6 +392,92 @@ namespace Apache.Ignite.Core.Tests.Binary } } + private class EnumsBinaryForm + { + public IBinaryObject Byte { get; set; } + public IBinaryObject SByte { get; set; } + public IBinaryObject Short { get; set; } + public IBinaryObject UShort { get; set; } + public IBinaryObject Int { get; set; } + public IBinaryObject UInt { get; set; } + public IBinaryObject Long { get; set; } + public IBinaryObject ULong { get; set; } + + private bool Equals(EnumsBinaryForm other) + { + return Equals(Byte, other.Byte) && Equals(SByte, other.SByte) && Equals(Short, other.Short) && + Equals(UShort, other.UShort) && Equals(Int, other.Int) && Equals(UInt, other.UInt) && + Equals(Long, other.Long) && Equals(ULong, other.ULong); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((EnumsBinaryForm) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Byte != null ? Byte.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (SByte != null ? SByte.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Short != null ? Short.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (UShort != null ? UShort.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Int != null ? Int.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (UInt != null ? UInt.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Long != null ? Long.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ULong != null ? ULong.GetHashCode() : 0); + return hashCode; + } + } + } + + private class EnumsBinarizableNullable + { + public ByteEnum? Byte { get; set; } + public SByteEnum? SByte { get; set; } + public ShortEnum? Short { get; set; } + public UShortEnum? UShort { get; set; } + public IntEnum? Int { get; set; } + public UIntEnum? UInt { get; set; } + public LongEnum? Long { get; set; } + public ULongEnum? ULong { get; set; } + + private bool Equals(EnumsBinarizableNullable other) + { + return Byte == other.Byte && SByte == other.SByte && Short == other.Short + && UShort == other.UShort && Int == other.Int && UInt == other.UInt + && Long == other.Long && ULong == other.ULong; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((EnumsBinarizableNullable) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Byte.GetHashCode(); + hashCode = (hashCode*397) ^ SByte.GetHashCode(); + hashCode = (hashCode*397) ^ Short.GetHashCode(); + hashCode = (hashCode*397) ^ UShort.GetHashCode(); + hashCode = (hashCode*397) ^ Int.GetHashCode(); + hashCode = (hashCode*397) ^ UInt.GetHashCode(); + hashCode = (hashCode*397) ^ Long.GetHashCode(); + hashCode = (hashCode*397) ^ ULong.GetHashCode(); + return hashCode; + } + } + } + [Serializable] private class EnumsSerializable : EnumsBinarizable, ISerializable { @@ -272,5 +510,38 @@ namespace Apache.Ignite.Core.Tests.Binary info.AddValue("ulong", ULong); } } + + [Serializable] + private class EnumsSerializableNullable : EnumsBinarizableNullable, ISerializable + { + public EnumsSerializableNullable() + { + // No-op. + } + + protected EnumsSerializableNullable(SerializationInfo info, StreamingContext context) + { + Byte = (ByteEnum?) info.GetValue("byte", typeof(ByteEnum)); + SByte = (SByteEnum?) info.GetValue("sbyte", typeof(SByteEnum)); + Short = (ShortEnum?) info.GetValue("short", typeof(ShortEnum)); + UShort = (UShortEnum?) info.GetValue("ushort", typeof(UShortEnum)); + Int = (IntEnum?) info.GetValue("int", typeof(IntEnum)); + UInt = (UIntEnum?) info.GetValue("uint", typeof(UIntEnum)); + Long = (LongEnum?) info.GetValue("long", typeof(LongEnum)); + ULong = (ULongEnum?) info.GetValue("ulong", typeof(ULongEnum)); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("byte", Byte); + info.AddValue("sbyte", SByte); + info.AddValue("short", Short); + info.AddValue("ushort", UShort); + info.AddValue("int", Int); + info.AddValue("uint", UInt); + info.AddValue("long", Long); + info.AddValue("ulong", ULong); + } + } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/34409fd5/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/EnumsTestOnline.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/EnumsTestOnline.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/EnumsTestOnline.cs new file mode 100644 index 0000000..0c82696 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/EnumsTestOnline.cs @@ -0,0 +1,45 @@ +/* + * 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 +{ + using NUnit.Framework; + + /// <summary> + /// Enums test with Ignite running. + /// </summary> + public class EnumsTestOnline : EnumsTest + { + /// <summary> + /// Sets up the fixture. + /// </summary> + [TestFixtureSetUp] + public void FixtureSetUp() + { + Ignition.Start(TestUtils.GetTestConfiguration()); + } + + /// <summary> + /// Tears down the fixture. + /// </summary> + [TestFixtureTearDown] + public void FixtureTearDown() + { + Ignition.StopAll(true); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/34409fd5/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs index e38085c..4b4b471 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs @@ -401,9 +401,10 @@ namespace Apache.Ignite.Core.Impl.Binary return default(T); case BinaryUtils.TypeEnum: + return ReadEnum0<T>(this, _mode == BinaryMode.ForceBinary); + case BinaryUtils.TypeBinaryEnum: - // Never read enums in binary mode when reading a field (we do not support half-binary objects) - return ReadEnum0<T>(this, false); + return ReadEnum0<T>(this, _mode != BinaryMode.Deserialize); case BinaryUtils.HdrFull: // Unregistered enum written as serializable @@ -583,6 +584,10 @@ namespace Apache.Ignite.Core.Impl.Binary return true; case BinaryUtils.TypeEnum: + res = ReadEnum0<T>(this, _mode == BinaryMode.ForceBinary); + + return true; + case BinaryUtils.TypeBinaryEnum: res = ReadEnum0<T>(this, _mode != BinaryMode.Deserialize); http://git-wip-us.apache.org/repos/asf/ignite/blob/34409fd5/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 b061be2..97964cf 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs @@ -422,11 +422,7 @@ namespace Apache.Ignite.Core.Impl.Binary { var type = field.FieldType; - var genericDef = type.IsGenericType ? type.GetGenericTypeDefinition() : null; - - bool nullable = genericDef == typeof (Nullable<>); - - var nullableType = nullable ? type.GetGenericArguments()[0] : null; + var nullableType = Nullable.GetUnderlyingType(type); if (type == typeof (decimal)) { @@ -455,14 +451,15 @@ namespace Apache.Ignite.Core.Impl.Binary ? GetRawReader(field, r => r.ReadObject<Guid>()) : GetReader(field, (f, r) => r.ReadObject<Guid>(f)); } - else if (nullable && nullableType == typeof (Guid)) + else if (nullableType == typeof (Guid)) { writeAction = raw ? GetRawWriter<Guid?>(field, (w, o) => w.WriteGuid(o)) : GetWriter<Guid?>(field, (f, w, o) => w.WriteGuid(f, o)); readAction = raw ? GetRawReader(field, r => r.ReadGuid()) : GetReader(field, (f, r) => r.ReadGuid(f)); } - else if (type.IsEnum && !new[] {typeof(long), typeof(ulong)}.Contains(Enum.GetUnderlyingType(type))) + else if ((nullableType ?? type).IsEnum && + !new[] {typeof(long), typeof(ulong)}.Contains(Enum.GetUnderlyingType(nullableType ?? type))) { writeAction = raw ? GetRawWriter<object>(field, (w, o) => w.WriteEnum(o), true) http://git-wip-us.apache.org/repos/asf/ignite/blob/34409fd5/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 2ac617e..91a536e 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs @@ -1787,6 +1787,8 @@ namespace Apache.Ignite.Core.Impl.Binary { Debug.Assert(type != null); + type = Nullable.GetUnderlyingType(type) ?? type; + if (!type.IsEnum) return false; http://git-wip-us.apache.org/repos/asf/ignite/blob/34409fd5/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 da2b371..43a73912 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs @@ -861,7 +861,9 @@ namespace Apache.Ignite.Core.Impl.Binary WriteNullField(); else { - var type = val.GetType(); + // Unwrap nullable. + var valType = val.GetType(); + var type = Nullable.GetUnderlyingType(valType) ?? valType; if (!type.IsEnum) {
