using System;
using System.Collections.Generic;
using System.ComponentModel;
using JD.Collections.Extensions;
using JD.Kernel.StaticHelpers;
using JD.Reflection;
namespace JD.TeaBoxAddinSystem.Addins
{
///
/// An object that can convert CLR properties to/from strings.
///
public class StringPropertyConverter
{
#region Delegates
///
/// Converts a string into a custom object.
///
///
///
///
///
public delegate bool CustomValueDeserializationDelegate(Type targetType, string value, out object convertedValue);
#endregion
#region Variables
private readonly TypeConverterCache _typeConverter = new TypeConverterCache();
private Action m_errorLogger;
private Action m_superfluousPropertyLogger;
private CustomValueDeserializationDelegate m_customConverter;
private bool m_superfluousPropertiesProduceErrors;
#endregion
#region Properties
///
/// Error Logger.
///
public Action ErrorLogger
{
get
{
return m_errorLogger;
}
set
{
m_errorLogger = value;
}
}
///
/// Superfluous Property Logger. Used when deserializing from string dictionaries.
///
public Action SuperfluousPropertyLogger
{
get
{
return m_superfluousPropertyLogger;
}
set
{
m_superfluousPropertyLogger = value;
}
}
///
/// Custom Converter.
///
public CustomValueDeserializationDelegate CustomConverter
{
get
{
return m_customConverter;
}
set
{
m_customConverter = value;
}
}
///
/// Whether to log errors about properties that do not have corresponding property descriptors.
///
public bool SuperfluousPropertiesProduceErrors
{
get
{
return m_superfluousPropertiesProduceErrors;
}
set
{
m_superfluousPropertiesProduceErrors = value;
}
}
#endregion
#region Methods
///
/// Serializes the given into the given using the given .
///
///
///
///
///
public bool Serialize(object instance, IEnumerable descriptors, IDictionary properties)
{
Throw.IfNull(instance, "instance");
Throw.IfNull(descriptors, "descriptors");
Throw.IfNull(properties, "properties");
Throw.InvalidArgumentIf(properties.IsReadOnly, "Properties are read-only.", "properties");
bool errorEncountered = false;
foreach (StringPropertyDescriptor propertyDescriptor in descriptors)
{
System.Diagnostics.Debug.Assert(propertyDescriptor.MemberInfo != null);
string value;
if (!GetValue(instance, propertyDescriptor, out value))
{
errorEncountered = true;
continue;
}
properties[propertyDescriptor.Name] = value;
}
return !errorEncountered;
}
///
/// Deserializes the given using the given
///
///
///
///
public bool Deserialize(object instance, IEnumerable descriptors, IDictionary properties)
{
IReadOnlyDictionary dict = properties.AsReadOnly();
return Deserialize(instance, descriptors, dict);
}
///
/// Deserializes the given using the given
///
///
///
///
public bool Deserialize(object instance, IEnumerable descriptors, IReadOnlyDictionary properties)
{
Throw.IfNull(instance, "instance");
Throw.IfNull(descriptors, "descriptors");
Throw.IfNull(properties, "properties");
bool errorEncountered = false;
Dictionary unusedAttributes = null;
if (SuperfluousPropertiesProduceErrors)
{
unusedAttributes = CreateDictionary(properties);
}
foreach (StringPropertyDescriptor propertyDescriptor in descriptors)
{
System.Diagnostics.Debug.Assert(propertyDescriptor.MemberInfo != null);
string name = propertyDescriptor.Name;
string value;
if (properties.TryGetValue(name, out value))
{
errorEncountered |= !SetValue(instance, propertyDescriptor, value);
if (unusedAttributes != null)
{
unusedAttributes.Remove(name);
}
}
else if (propertyDescriptor.Attribute.Required)
{
LogError($"Attribute {name} is required by instance of type {instance.GetType().FullName}");
errorEncountered = true;
}
}
if (unusedAttributes != null)
{
foreach (KeyValuePair unusedAttribute in unusedAttributes)
{
LogSuperfluousProperty(unusedAttribute.Key);
}
}
return !errorEncountered;
}
private static Dictionary CreateDictionary(IEnumerable> properties)
{
var unusedAttributes = new Dictionary();
unusedAttributes.AddRange(properties);
return unusedAttributes;
}
///
/// Gets the value of the given for the given .
///
///
///
///
///
public bool GetValue(object instance, StringPropertyDescriptor descriptor, out string value)
{
Throw.IfNull(instance, "instance");
Throw.IfNull(descriptor, "descriptor");
Throw.InvalidOperationIf(descriptor.MemberInfo.IsWriteOnly, "Descriptor is write-only.");
Throw.InvalidOperationIf(descriptor.MemberInfo.InnerMemberInfo.DeclaringType.IsAssignableFrom(instance.GetType()), "Instance type is invalid.");
try
{
value = ConvertValue(descriptor.MemberInfo.GetValue(instance));
return true;
}
catch
{
LogError($"Cannot get value for property {descriptor.Name} of type {instance.GetType().FullName}");
value = null;
return false;
}
}
///
/// Sets the value of the given to the given , for the given .
///
///
///
///
///
public bool SetValue(object instance, StringPropertyDescriptor descriptor, string value)
{
Throw.IfNull(instance, "instance");
Throw.IfNull(descriptor, "descriptor");
Throw.InvalidOperationIf(descriptor.MemberInfo.IsReadOnly, "Descriptor is read-only.");
Throw.InvalidOperationIf(!descriptor.MemberInfo.InnerMemberInfo.DeclaringType.IsAssignableFrom(instance.GetType()), "Instance type is invalid.");
Type targetType = descriptor.MemberInfo.ValueType;
object convertedValue = null;
try
{
System.Diagnostics.Debug.Assert(targetType != null);
convertedValue = ConvertValue(targetType, value);
}
catch
{
LogError($"Cannot convert value {value} to type {targetType.FullName} for attribute {descriptor.Name} of type {instance.GetType().FullName}.");
return false;
}
try
{
descriptor.MemberInfo.SetValue(instance, convertedValue);
}
catch (Exception e)
{
LogError($"Unexpected exception while setting value {value} to attribute {descriptor.Name} of type {instance.GetType().FullName}: {e.Message}");
return false;
}
return true;
}
///
/// Converts the given into a string.
///
///
///
public string ConvertValue(object value)
{
if (value != null)
{
TypeConverter c = _typeConverter.GetConverter(value.GetType());
return c.ConvertToInvariantString(value);
}
else
{
return null;
}
}
///
/// Converts the given into the given .
///
///
///
///
public object ConvertValue(Type targetType, string value)
{
Throw.IfNull(targetType, "targetType");
// Optim: most fields are simple types
if (targetType == typeof(string))
{
return value;
}
if (targetType.IsArray)
{
Type elementType = targetType.GetElementType();
if (string.IsNullOrEmpty(value))
{
return Array.CreateInstance(elementType, 0);
}
string[] elements = value.Split(',');
Array array = Array.CreateInstance(elementType, elements.Length);
for (int index = 0; index < elements.Length; index++)
{
array.SetValue(ConvertValue(elementType, elements[index].Trim()), index);
}
return array;
}
object convertedValue;
if (CustomConverter != null && CustomConverter(targetType, value, out convertedValue))
{
return convertedValue;
}
TypeConverter c = _typeConverter.GetConverter(targetType);
return c.ConvertFromInvariantString(value);
}
private void LogError(string errorMessage)
{
if (ErrorLogger != null)
{
ErrorLogger(errorMessage);
}
}
private void LogSuperfluousProperty(string property)
{
if (SuperfluousPropertyLogger != null)
{
SuperfluousPropertyLogger(property);
}
}
#endregion
}
}