358 lines
12 KiB
C#
358 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using JD.Collections.Extensions;
|
|
using JD.Kernel.StaticHelpers;
|
|
using JD.Reflection;
|
|
|
|
namespace JD.TeaBoxAddinSystem.Addins
|
|
{
|
|
/// <summary>
|
|
/// An object that can convert CLR properties to/from strings.
|
|
/// </summary>
|
|
public class StringPropertyConverter
|
|
{
|
|
#region Delegates
|
|
|
|
/// <summary>
|
|
/// Converts a string into a custom object.
|
|
/// </summary>
|
|
/// <param name="targetType"></param>
|
|
/// <param name="value"></param>
|
|
/// <param name="convertedValue"></param>
|
|
/// <returns></returns>
|
|
public delegate bool CustomValueDeserializationDelegate(Type targetType, string value, out object convertedValue);
|
|
|
|
#endregion
|
|
|
|
#region Variables
|
|
private readonly TypeConverterCache _typeConverter = new TypeConverterCache();
|
|
|
|
private Action<string> m_errorLogger;
|
|
private Action<string> m_superfluousPropertyLogger;
|
|
private CustomValueDeserializationDelegate m_customConverter;
|
|
|
|
private bool m_superfluousPropertiesProduceErrors;
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Error Logger.
|
|
/// </summary>
|
|
public Action<string> ErrorLogger
|
|
{
|
|
get
|
|
{
|
|
return m_errorLogger;
|
|
}
|
|
set
|
|
{
|
|
m_errorLogger = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Superfluous Property Logger. Used when deserializing from string dictionaries.
|
|
/// </summary>
|
|
public Action<string> SuperfluousPropertyLogger
|
|
{
|
|
get
|
|
{
|
|
return m_superfluousPropertyLogger;
|
|
}
|
|
set
|
|
{
|
|
m_superfluousPropertyLogger = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Custom Converter.
|
|
/// </summary>
|
|
public CustomValueDeserializationDelegate CustomConverter
|
|
{
|
|
get
|
|
{
|
|
return m_customConverter;
|
|
}
|
|
set
|
|
{
|
|
m_customConverter = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether to log errors about properties that do not have corresponding property descriptors.
|
|
/// </summary>
|
|
public bool SuperfluousPropertiesProduceErrors
|
|
{
|
|
get
|
|
{
|
|
return m_superfluousPropertiesProduceErrors;
|
|
}
|
|
set
|
|
{
|
|
m_superfluousPropertiesProduceErrors = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Serializes the given <paramref name="instance"/> into the given <paramref name="properties"/> using the given <paramref name="descriptors"/>.
|
|
/// </summary>
|
|
/// <param name="instance"></param>
|
|
/// <param name="descriptors"></param>
|
|
/// <param name="properties"></param>
|
|
/// <returns></returns>
|
|
public bool Serialize(object instance, IEnumerable<StringPropertyDescriptor> descriptors, IDictionary<string, string> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes the given <paramref name="instance"/> using the given <paramref name="properties"/>
|
|
/// </summary>
|
|
/// <param name="instance"></param>
|
|
/// <param name="descriptors"></param>
|
|
/// <param name="properties"></param>
|
|
public bool Deserialize(object instance, IEnumerable<StringPropertyDescriptor> descriptors, IDictionary<string, string> properties)
|
|
{
|
|
IReadOnlyDictionary<string, string> dict = properties.AsReadOnly();
|
|
return Deserialize(instance, descriptors, dict);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes the given <paramref name="instance"/> using the given <paramref name="properties"/>
|
|
/// </summary>
|
|
/// <param name="instance"></param>
|
|
/// <param name="descriptors"></param>
|
|
/// <param name="properties"></param>
|
|
public bool Deserialize(object instance, IEnumerable<StringPropertyDescriptor> descriptors, IReadOnlyDictionary<string, string> properties)
|
|
{
|
|
Throw.IfNull(instance, "instance");
|
|
Throw.IfNull(descriptors, "descriptors");
|
|
Throw.IfNull(properties, "properties");
|
|
|
|
bool errorEncountered = false;
|
|
|
|
Dictionary<string, string> 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<string, string> unusedAttribute in unusedAttributes)
|
|
{
|
|
LogSuperfluousProperty(unusedAttribute.Key);
|
|
}
|
|
}
|
|
|
|
return !errorEncountered;
|
|
}
|
|
|
|
private static Dictionary<string, string> CreateDictionary(IEnumerable<KeyValuePair<string, string>> properties)
|
|
{
|
|
var unusedAttributes = new Dictionary<string, string>();
|
|
unusedAttributes.AddRange(properties);
|
|
return unusedAttributes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the value of the given <paramref name="descriptor"/> for the given <paramref name="instance"/>.
|
|
/// </summary>
|
|
/// <param name="instance"></param>
|
|
/// <param name="descriptor"></param>
|
|
/// <param name="value"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the value of the given <paramref name="descriptor"/> to the given <paramref name="value"/>, for the given <paramref name="instance"/>.
|
|
/// </summary>
|
|
/// <param name="instance"></param>
|
|
/// <param name="descriptor"></param>
|
|
/// <param name="value"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the given <paramref name="value"/> into a string.
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <returns></returns>
|
|
public string ConvertValue(object value)
|
|
{
|
|
if (value != null)
|
|
{
|
|
TypeConverter c = _typeConverter.GetConverter(value.GetType());
|
|
return c.ConvertToInvariantString(value);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the given <paramref name="value"/> into the given <paramref name="targetType"/>.
|
|
/// </summary>
|
|
/// <param name="targetType"></param>
|
|
/// <param name="value"></param>
|
|
/// <returns></returns>
|
|
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
|
|
}
|
|
}
|