You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2017/03/22 11:13:02 UTC

[16/23] ignite git commit: IGNITE-4846 .NET: Support complex type dictionaries in app.config configuration

IGNITE-4846 .NET: Support complex type dictionaries in app.config configuration

This closes #1653


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

Branch: refs/heads/ignite-4565-ddl
Commit: 3b89a5ce78fadf4e39c8c927afd8ffef7b9fa186
Parents: b4cc8a7
Author: Pavel Tupitsyn <pt...@apache.org>
Authored: Tue Mar 21 18:08:03 2017 +0300
Committer: Pavel Tupitsyn <pt...@apache.org>
Committed: Tue Mar 21 18:08:03 2017 +0300

----------------------------------------------------------------------
 .../IgniteConfigurationSerializerTest.cs        |  44 +++-
 .../Common/IgniteConfigurationXmlSerializer.cs  | 250 ++++++++++++-------
 2 files changed, 204 insertions(+), 90 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/3b89a5ce/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
index 2f9366e..4335d11 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs
@@ -126,7 +126,10 @@ namespace Apache.Ignite.Core.Tests
                                 <int>TaskFailed</int>
                                 <int>JobFinished</int>
                             </includedEventTypes>
-                            <userAttributes><pair key='myNode' value='true' /></userAttributes>
+                            <userAttributes>
+                                <pair key='myNode' value='true' />
+                                <pair key='foo'><value type='Apache.Ignite.Core.Tests.IgniteConfigurationSerializerTest+FooClass, Apache.Ignite.Core.Tests'><bar>Baz</bar></value></pair>
+                            </userAttributes>
                             <atomicConfiguration backups='2' cacheMode='Local' atomicSequenceReserveSize='250' />
                             <transactionConfiguration defaultTransactionConcurrency='Optimistic' defaultTransactionIsolation='RepeatableRead' defaultTimeout='0:1:2' pessimisticTransactionLogSize='15' pessimisticTransactionLogLinger='0:0:33' />
                             <logger type='Apache.Ignite.Core.Tests.IgniteConfigurationSerializerTest+TestLogger, Apache.Ignite.Core.Tests' />
@@ -202,7 +205,11 @@ namespace Apache.Ignite.Core.Tests
             Assert.AreEqual(99, af.Partitions);
             Assert.IsTrue(af.ExcludeNeighbors);
 
-            Assert.AreEqual(new Dictionary<string, object> {{"myNode", "true"}}, cfg.UserAttributes);
+            Assert.AreEqual(new Dictionary<string, object>
+            {
+                {"myNode", "true"},
+                {"foo", new FooClass {Bar = "Baz"}}
+            }, cfg.UserAttributes);
 
             var atomicCfg = cfg.AtomicConfiguration;
             Assert.AreEqual(2, atomicCfg.Backups);
@@ -544,8 +551,9 @@ namespace Apache.Ignite.Core.Tests
                     Assert.IsNull(xVal);
                     Assert.IsNull(yVal);
                 }
-                else if (propType != typeof(string) && propType.IsGenericType 
-                    && propType.GetGenericTypeDefinition() == typeof (ICollection<>))
+                else if (propType != typeof(string) && propType.IsGenericType &&
+                         (propType.GetGenericTypeDefinition() == typeof(ICollection<>) ||
+                          propType.GetGenericTypeDefinition() == typeof(IDictionary<,>) ))
                 {
                     var xCol = ((IEnumerable) xVal).OfType<object>().ToList();
                     var yCol = ((IEnumerable) yVal).OfType<object>().ToList();
@@ -738,7 +746,8 @@ namespace Apache.Ignite.Core.Tests
                 SuppressWarnings = true,
                 WorkDirectory = @"c:\work",
                 IsDaemon = true,
-                UserAttributes = Enumerable.Range(1, 10).ToDictionary(x => x.ToString(), x => (object) x),
+                UserAttributes = Enumerable.Range(1, 10).ToDictionary(x => x.ToString(),
+                    x => x%2 == 0 ? (object) x : new FooClass {Bar = x.ToString()}),
                 AtomicConfiguration = new AtomicConfiguration
                 {
                     CacheMode = CacheMode.Replicated,
@@ -904,7 +913,30 @@ namespace Apache.Ignite.Core.Tests
         /// </summary>
         public class FooClass
         {
-            // No-op.
+            public string Bar { get; set; }
+
+            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 string.Equals(Bar, ((FooClass) obj).Bar);
+            }
+
+            public override int GetHashCode()
+            {
+                return Bar != null ? Bar.GetHashCode() : 0;
+            }
+
+            public static bool operator ==(FooClass left, FooClass right)
+            {
+                return Equals(left, right);
+            }
+
+            public static bool operator !=(FooClass left, FooClass right)
+            {
+                return !Equals(left, right);
+            }
         }
 
         /// <summary>

http://git-wip-us.apache.org/repos/asf/ignite/blob/3b89a5ce/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/IgniteConfigurationXmlSerializer.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/IgniteConfigurationXmlSerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/IgniteConfigurationXmlSerializer.cs
index feb0f9e..6c5d620 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/IgniteConfigurationXmlSerializer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/IgniteConfigurationXmlSerializer.cs
@@ -153,9 +153,19 @@ namespace Apache.Ignite.Core.Impl.Common
         {
             var props = GetNonDefaultProperties(obj).OrderBy(x => x.Name).ToList();
 
-            // Specify type for interfaces and abstract classes
-            if (valueType.IsAbstract)
+            var realType = obj.GetType();
+
+            // Specify type when it differs from declared type.
+            if (valueType != realType)
+            {
                 writer.WriteAttributeString(TypNameAttribute, TypeStringConverter.Convert(obj.GetType()));
+            }
+
+            if (IsBasicType(obj.GetType()))
+            {
+                WriteBasicProperty(obj, writer, realType, null);
+                return;
+            }
 
             // Write attributes
             foreach (var prop in props.Where(p => IsBasicType(p.PropertyType) && !IsObsolete(p)))
@@ -181,10 +191,14 @@ namespace Apache.Ignite.Core.Impl.Common
             // Read attributes
             while (reader.MoveToNextAttribute())
             {
-                var name = reader.Name;
-                var val = reader.Value;
+                if (reader.Name == TypNameAttribute || reader.Name == XmlnsAttribute)
+                    continue;
+
+                var prop = GetPropertyOrThrow(reader.Name, reader.Value, target.GetType());
+
+                var value = ConvertBasicValue(reader.Value, prop, prop.PropertyType);
 
-                SetProperty(target, name, val);
+                prop.SetValue(target, value, null);
             }
 
             // Read content
@@ -195,64 +209,56 @@ namespace Apache.Ignite.Core.Impl.Common
                 if (reader.NodeType != XmlNodeType.Element)
                     continue;
 
-                var name = reader.Name;
-                var prop = GetPropertyOrThrow(name, reader.Value, targetType);
-                var propType = prop.PropertyType;
+                var prop = GetPropertyOrThrow(reader.Name, reader.Value, targetType);
 
-                if (IsBasicType(propType))
-                {
-                    // Regular property in xmlElement form
-                    SetProperty(target, name, reader.ReadString());
-                }
-                else if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof (ICollection<>))
-                {
-                    // Collection
-                    ReadCollectionProperty(reader, prop, target, resolver);
-                }
-                else if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof (IDictionary<,>))
-                {
-                    // Dictionary
-                    ReadDictionaryProperty(reader, prop, target);
-                }
-                else
-                {
-                    // Nested object (complex property)
-                    prop.SetValue(target, ReadComplexProperty(reader, propType, prop.Name, targetType, resolver), null);
-                }
+                var value = ReadPropertyValue(reader, resolver, prop, targetType);
+
+                prop.SetValue(target, value, null);
             }
         }
 
         /// <summary>
-        /// Reads the complex property (nested object).
+        /// Reads the property value.
         /// </summary>
-        private static object ReadComplexProperty(XmlReader reader, Type propType, string propName, Type targetType, 
-            TypeResolver resolver)
+        private static object ReadPropertyValue(XmlReader reader, TypeResolver resolver, 
+            PropertyInfo prop, Type targetType)
         {
-            if (propType.IsAbstract)
+            var propType = prop.PropertyType;
+
+            if (propType == typeof(object))
             {
-                var typeName = reader.GetAttribute(TypNameAttribute);
+                propType = ResolvePropertyType(reader, propType, prop.Name, targetType, resolver);
+            }
 
-                var derivedTypes = GetConcreteDerivedTypes(propType);
+            if (IsBasicType(propType))
+            {
+                // Regular property in xmlElement form.
+                return ConvertBasicValue(reader.ReadString(), prop, propType);
+            }
 
-                propType = typeName == null
-                    ? null
-                    : resolver.ResolveType(typeName) ?? derivedTypes.FirstOrDefault(x => x.Name == typeName);
+            if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(ICollection<>))
+            {
+                // Collection.
+                return ReadCollectionProperty(reader, prop, targetType, resolver);
+            }
 
-                if (propType == null)
-                {
-                    var message = string.Format("'type' attribute is required for '{0}.{1}' property", targetType.Name,
-                        propName);
+            if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
+            {
+                // Dictionary.
+                return ReadDictionaryProperty(reader, prop, resolver);
+            }
 
-                    if (typeName != null)
-                    {
-                        message += ", specified type cannot be resolved: " + typeName;
-                    }
-                    else if (derivedTypes.Any())
-                        message += ", possible values are: " + string.Join(", ", derivedTypes.Select(x => x.Name));
+            // Nested object (complex property).
+            return ReadComplexProperty(reader, propType, prop.Name, targetType, resolver);
+        }
 
-                    throw new ConfigurationErrorsException(message);
-                }
-            }
+        /// <summary>
+        /// Reads the complex property (nested object).
+        /// </summary>
+        private static object ReadComplexProperty(XmlReader reader, Type propType, string propName, Type targetType, 
+            TypeResolver resolver)
+        {
+            propType = ResolvePropertyType(reader, propType, propName, targetType, resolver);
 
             var nestedVal = Activator.CreateInstance(propType);
 
@@ -267,9 +273,46 @@ namespace Apache.Ignite.Core.Impl.Common
         }
 
         /// <summary>
+        /// Resolves the type of the property.
+        /// </summary>
+        private static Type ResolvePropertyType(XmlReader reader, Type propType, string propName, Type targetType,
+            TypeResolver resolver)
+        {
+            var typeName = reader.GetAttribute(TypNameAttribute);
+
+            if (!propType.IsAbstract && typeName == null)
+                return propType;
+
+            var res = typeName == null
+                ? null
+                : resolver.ResolveType(typeName) ??
+                  GetConcreteDerivedTypes(propType).FirstOrDefault(x => x.Name == typeName);
+
+            if (res != null)
+                return res;
+
+            var message = string.Format("'type' attribute is required for '{0}.{1}' property", targetType.Name,
+                propName);
+
+            var derivedTypes = GetConcreteDerivedTypes(propType);
+
+
+            if (typeName != null)
+            {
+                message += ", specified type cannot be resolved: " + typeName;
+            }
+            else if (derivedTypes.Any())
+            {
+                message += ", possible values are: " + string.Join(", ", derivedTypes.Select(x => x.Name));
+            }
+
+            throw new ConfigurationErrorsException(message);
+        }
+
+        /// <summary>
         /// Reads the collection.
         /// </summary>
-        private static void ReadCollectionProperty(XmlReader reader, PropertyInfo prop, object target, 
+        private static IList ReadCollectionProperty(XmlReader reader, PropertyInfo prop, Type targetType, 
             TypeResolver resolver)
         {
             var elementType = prop.PropertyType.GetGenericArguments().Single();
@@ -295,22 +338,24 @@ namespace Apache.Ignite.Core.Impl.Common
 
                     list.Add(converter != null
                         ? converter.ConvertFromInvariantString(subReader.ReadString())
-                        : ReadComplexProperty(subReader, elementType, prop.Name, target.GetType(), resolver));
+                        : ReadComplexProperty(subReader, elementType, prop.Name, targetType, resolver));
                 }
             }
 
-            prop.SetValue(target, list, null);
+            return list;
         }
         
         /// <summary>
         /// Reads the dictionary.
         /// </summary>
-        private static void ReadDictionaryProperty(XmlReader reader, PropertyInfo prop, object target)
+        private static IDictionary ReadDictionaryProperty(XmlReader reader, PropertyInfo prop, TypeResolver resolver)
         {
             var keyValTypes = prop.PropertyType.GetGenericArguments();
 
             var dictType = typeof (Dictionary<,>).MakeGenericType(keyValTypes);
 
+            var pairType = typeof(Pair<,>).MakeGenericType(keyValTypes);
+
             var dict = (IDictionary) Activator.CreateInstance(dictType);
 
             using (var subReader = reader.ReadSubtree())
@@ -326,42 +371,29 @@ namespace Apache.Ignite.Core.Impl.Common
                             string.Format("Invalid dictionary element in IgniteConfiguration: expected '{0}', " +
                                           "but was '{1}'", KeyValPairElement, subReader.Name));
 
-                    var key = subReader.GetAttribute("key");
+                    var pair = (IPair) Activator.CreateInstance(pairType);
 
-                    if (key == null)
-                        throw new ConfigurationErrorsException(
-                            "Invalid dictionary entry, key attribute is missing for property " + prop);
+                    var pairReader = subReader.ReadSubtree();
 
-                    dict[key] = subReader.GetAttribute("value");
+                    pairReader.Read();
+
+                    ReadElement(pairReader, pair, resolver);
+
+                    dict[pair.Key] = pair.Value;
                 }
             }
 
-            prop.SetValue(target, dict, null);
+            return dict;
         }
 
         /// <summary>
-        /// Sets the property.
+        /// Reads the basic value.
         /// </summary>
-        private static void SetProperty(object target, string propName, string propVal)
+        private static object ConvertBasicValue(string propVal, PropertyInfo property, Type propertyType)
         {
-            if (propName == TypNameAttribute || propName == XmlnsAttribute)
-                return;
+            var converter = GetConverter(property, propertyType);
 
-            var type = target.GetType();
-            var property = GetPropertyOrThrow(propName, propVal, type);
-
-            if (!property.CanWrite)
-            {
-                throw new ConfigurationErrorsException(string.Format(
-                        "Invalid IgniteConfiguration attribute '{0}={1}', property '{2}.{3}' is not writeable",
-                        propName, propVal, type, property.Name));
-            }
-
-            var converter = GetConverter(property, property.PropertyType);
-
-            var convertedVal = converter.ConvertFromInvariantString(propVal);
-
-            property.SetValue(target, convertedVal, null);
+            return converter.ConvertFromInvariantString(propVal);
         }
 
         /// <summary>
@@ -376,15 +408,24 @@ namespace Apache.Ignite.Core.Impl.Common
         /// <summary>
         /// Gets specified property from a type or throws an exception.
         /// </summary>
-        private static PropertyInfo GetPropertyOrThrow(string propName, string propVal, Type type)
+        private static PropertyInfo GetPropertyOrThrow(string propName, object propVal, Type type)
         {
             var property = type.GetProperty(XmlNameToPropertyName(propName));
 
             if (property == null)
+            {
                 throw new ConfigurationErrorsException(
                     string.Format(
                         "Invalid IgniteConfiguration attribute '{0}={1}', there is no such property on '{2}'",
                         propName, propVal, type));
+            }
+
+            if (!property.CanWrite)
+            {
+                throw new ConfigurationErrorsException(string.Format(
+                        "Invalid IgniteConfiguration attribute '{0}={1}', property '{2}.{3}' is not writeable",
+                        propName, propVal, type, property.Name));
+            }
 
             return property;
         }
@@ -425,8 +466,7 @@ namespace Apache.Ignite.Core.Impl.Common
             if (IsKeyValuePair(propertyType))
                 return false;
 
-            return propertyType.IsValueType || propertyType == typeof (string) || propertyType == typeof (Type) ||
-                   propertyType == typeof (object);
+            return propertyType.IsValueType || propertyType == typeof (string) || propertyType == typeof (Type);
         }
 
         /// <summary>
@@ -444,7 +484,6 @@ namespace Apache.Ignite.Core.Impl.Common
         /// </summary>
         private static TypeConverter GetConverter(PropertyInfo property, Type propertyType)
         {
-            Debug.Assert(property != null);
             Debug.Assert(propertyType != null);
 
             if (propertyType.IsEnum)
@@ -456,7 +495,8 @@ namespace Apache.Ignite.Core.Impl.Common
             if (propertyType == typeof(bool))
                 return BooleanLowerCaseConverter.Instance;
 
-            if (property.DeclaringType == typeof (IgniteConfiguration) && property.Name == "IncludedEventTypes")
+            if (property != null &&
+                property.DeclaringType == typeof (IgniteConfiguration) && property.Name == "IncludedEventTypes")
                 return EventTypeConverter.Instance;
 
             if (propertyType == typeof (object))
@@ -508,5 +548,47 @@ namespace Apache.Ignite.Core.Impl.Common
 
             return property.GetCustomAttributes(typeof(ObsoleteAttribute), true).Any();
         }
+
+        /// <summary>
+        /// Non-generic Pair accessor.
+        /// </summary>
+        private interface IPair
+        {
+            /// <summary>
+            /// Gets the key.
+            /// </summary>
+            object Key { get; }
+
+            /// <summary>
+            /// Gets the value.
+            /// </summary>
+            object Value { get; }
+        }
+
+        /// <summary>
+        /// Surrogate dictionary entry to overcome immutable KeyValuePair.
+        /// </summary>
+        private class Pair<TK, TV> : IPair
+        {
+            // ReSharper disable once UnusedAutoPropertyAccessor.Local
+            // ReSharper disable once MemberCanBePrivate.Local
+            public TK Key { get; set; }
+
+            // ReSharper disable once UnusedAutoPropertyAccessor.Local
+            // ReSharper disable once MemberCanBePrivate.Local
+            public TV Value { get; set; }
+
+            /** <inheritdoc /> */
+            object IPair.Key
+            {
+                get { return Key; }
+            }
+
+            /** <inheritdoc /> */
+            object IPair.Value
+            {
+                get { return Value; }
+            }
+        }
     }
 }