ConfigProperty.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. #nullable enable
  2. using System.Reflection;
  3. using System.Text.Json;
  4. using System.Text.Json.Serialization;
  5. namespace Terminal.Gui;
  6. /// <summary>
  7. /// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> to
  8. /// get and set the property's value.
  9. /// </summary>
  10. /// <remarks>
  11. /// Configuration properties must be <see langword="public"/> and <see langword="static"/> and have the
  12. /// <see cref="SerializableConfigurationProperty"/> attribute. If the type of the property requires specialized JSON
  13. /// serialization, a <see cref="JsonConverter"/> must be provided using the <see cref="JsonConverterAttribute"/>
  14. /// attribute.
  15. /// </remarks>
  16. public class ConfigProperty
  17. {
  18. /// <summary>Describes the property.</summary>
  19. public PropertyInfo? PropertyInfo { get; set; }
  20. /// <summary>
  21. /// Holds the property's value as it was either read from the class's implementation or from a config file. If the
  22. /// property has not been set (e.g. because no configuration file specified a value), this will be
  23. /// <see langword="null"/>.
  24. /// </summary>
  25. /// <remarks>
  26. /// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements
  27. /// of the object that are non-null).
  28. /// </remarks>
  29. public object? PropertyValue { get; set; }
  30. /// <summary>Applies the <see cref="PropertyValue"/> to the static property described by <see cref="PropertyInfo"/>.</summary>
  31. /// <returns></returns>
  32. public bool Apply ()
  33. {
  34. if (PropertyValue is { })
  35. {
  36. try
  37. {
  38. if (PropertyInfo?.GetValue (null) is { })
  39. {
  40. PropertyInfo?.SetValue (null, DeepMemberWiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
  41. }
  42. }
  43. catch (TargetInvocationException tie)
  44. {
  45. // Check if there is an inner exception
  46. if (tie.InnerException is { })
  47. {
  48. // Handle the inner exception separately without catching the outer exception
  49. Exception? innerException = tie.InnerException;
  50. // Handle the inner exception here
  51. throw new JsonException (
  52. $"Error Applying Configuration Change: {innerException.Message}",
  53. innerException
  54. );
  55. }
  56. // Handle the outer exception or rethrow it if needed
  57. throw new JsonException ($"Error Applying Configuration Change: {tie.Message}", tie);
  58. }
  59. catch (ArgumentException ae)
  60. {
  61. throw new JsonException (
  62. $"Error Applying Configuration Change ({PropertyInfo?.Name}): {ae.Message}",
  63. ae
  64. );
  65. }
  66. }
  67. return PropertyValue != null;
  68. }
  69. /// <summary>
  70. /// Helper to get either the Json property named (specified by [JsonPropertyName(name)] or the actual property
  71. /// name.
  72. /// </summary>
  73. /// <param name="pi"></param>
  74. /// <returns></returns>
  75. public static string GetJsonPropertyName (PropertyInfo pi)
  76. {
  77. var attr = pi.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
  78. return attr?.Name ?? pi.Name;
  79. }
  80. /// <summary>
  81. /// Retrieves (using reflection) the value of the static property described in <see cref="PropertyInfo"/> into
  82. /// <see cref="PropertyValue"/>.
  83. /// </summary>
  84. /// <returns></returns>
  85. public object? RetrieveValue () { return PropertyValue = PropertyInfo!.GetValue (null); }
  86. /// <summary>
  87. /// Updates (using reflection) <see cref="PropertyValue"/> with
  88. /// the value in <paramref name="source"/>.
  89. /// </summary>
  90. /// <param name="source"></param>
  91. /// <returns></returns>
  92. /// <exception cref="ArgumentException"></exception>
  93. internal object? UpdateValueFrom (object source)
  94. {
  95. if (source is null)
  96. {
  97. return PropertyValue;
  98. }
  99. Type? ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
  100. if (source.GetType () != PropertyInfo!.PropertyType && ut is { } && source.GetType () != ut)
  101. {
  102. throw new ArgumentException (
  103. $"The source object ({
  104. PropertyInfo!.DeclaringType
  105. }.{
  106. PropertyInfo!.Name
  107. }) is not of type {
  108. PropertyInfo!.PropertyType
  109. }."
  110. );
  111. }
  112. if (PropertyValue is { })
  113. {
  114. PropertyValue = DeepMemberWiseCopy (source, PropertyValue);
  115. }
  116. else
  117. {
  118. PropertyValue = source;
  119. }
  120. return PropertyValue;
  121. }
  122. }