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