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; }
+ }
+ }
}
}