JD2022-TU1/main/tools/framework/JD.TeaBoxAddinSystem/Addins/StringPropertyConverter.cs

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