ConfigProperty.cs 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. using System;
  2. using System.Reflection;
  3. using System.Text.Json.Serialization;
  4. #nullable enable
  5. namespace Terminal.Gui;
  6. /// <summary>
  7. /// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/>
  8. /// to get and set the property's value.
  9. /// </summary>
  10. /// <remarks>
  11. /// Configuration properties must be <see langword="public"/> and <see langword="static"/>
  12. /// and have the <see cref="SerializableConfigurationProperty"/>
  13. /// attribute. If the type of the property requires specialized JSON serialization,
  14. /// a <see cref="JsonConverter"/> must be provided using
  15. /// the <see cref="JsonConverterAttribute"/> attribute.
  16. /// </remarks>
  17. public class ConfigProperty {
  18. private object? propertyValue;
  19. /// <summary>
  20. /// Describes the property.
  21. /// </summary>
  22. public PropertyInfo? PropertyInfo { get; set; }
  23. /// <summary>
  24. /// Helper to get either the Json property named (specified by [JsonPropertyName(name)]
  25. /// or the actual property name.
  26. /// </summary>
  27. /// <param name="pi"></param>
  28. /// <returns></returns>
  29. public static string GetJsonPropertyName (PropertyInfo pi)
  30. {
  31. var jpna = pi.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
  32. return jpna?.Name ?? pi.Name;
  33. }
  34. /// <summary>
  35. /// Holds the property's value as it was either read from the class's implementation or from a config file.
  36. /// If the property has not been set (e.g. because no configuration file specified a value),
  37. /// this will be <see langword="null"/>.
  38. /// </summary>
  39. /// <remarks>
  40. /// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements of
  41. /// the object that are non-null).
  42. /// </remarks>
  43. public object? PropertyValue {
  44. get => propertyValue;
  45. set {
  46. propertyValue = value;
  47. }
  48. }
  49. internal object? UpdateValueFrom (object source)
  50. {
  51. if (source == null) {
  52. return PropertyValue;
  53. }
  54. var ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
  55. if (source.GetType () != PropertyInfo!.PropertyType && (ut != null && source.GetType () != ut)) {
  56. throw new ArgumentException ($"The source object ({PropertyInfo!.DeclaringType}.{PropertyInfo!.Name}) is not of type {PropertyInfo!.PropertyType}.");
  57. }
  58. if (PropertyValue != null && source != null) {
  59. PropertyValue = ConfigurationManager.DeepMemberwiseCopy (source, PropertyValue);
  60. } else {
  61. PropertyValue = source;
  62. }
  63. return PropertyValue;
  64. }
  65. /// <summary>
  66. /// Retrieves (using reflection) the value of the static property described in <see cref="PropertyInfo"/>
  67. /// into <see cref="PropertyValue"/>.
  68. /// </summary>
  69. /// <returns></returns>
  70. public object? RetrieveValue ()
  71. {
  72. return PropertyValue = PropertyInfo!.GetValue (null);
  73. }
  74. /// <summary>
  75. /// Applies the <see cref="PropertyValue"/> to the property described by <see cref="PropertyInfo"/>.
  76. /// </summary>
  77. /// <returns></returns>
  78. public bool Apply ()
  79. {
  80. if (PropertyValue != null) {
  81. PropertyInfo?.SetValue (null, ConfigurationManager.DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
  82. }
  83. return PropertyValue != null;
  84. }
  85. }