| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- using System.Collections.Concurrent;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- namespace Terminal.Gui.Configuration;
- /// <summary>
- /// Defines a configuration settings scope. Classes that inherit from this abstract class can be used to define
- /// scopes for configuration settings. Each scope is a JSON object that contains a set of configuration settings.
- /// <para>
- /// When constructed, the dictionary will be populated with uninitialized configuration properties for the
- /// scope (<see cref="ConfigProperty.HasValue"/> will be <see langword="false"/>).
- /// </para>
- /// </summary>
- public class Scope<T> : ConcurrentDictionary<string, ConfigProperty>
- {
- /// <summary>
- /// Creates a new instance. The dictionary will be populated with uninitialized (<see cref="ConfigProperty.HasValue"/>
- /// will be <see langword="false"/>).
- /// </summary>
- [RequiresUnreferencedCode (
- "Uses cached configuration properties filtered by type T. This is AOT-safe as long as T is one of the known scope types (SettingsScope, ThemeScope, AppSettingsScope).")]
- public Scope () : base (StringComparer.InvariantCultureIgnoreCase) { }
- /// <summary>
- /// INTERNAL: Adds a new ConfigProperty given a <paramref name="value"/>. Determines the correct PropertyInfo etc... by
- /// retrieving the
- /// hard coded value for <paramref name="name"/>.
- /// </summary>
- /// <param name="name"></param>
- /// <param name="value"></param>
- internal void AddValue (string name, object? value)
- {
- ConfigProperty? configProperty = GetHardCodedProperty (name);
- if (configProperty is null)
- {
- throw new InvalidOperationException ($@"{name} is not a hard coded property.");
- }
- TryAdd (name, ConfigProperty.CreateCopy (configProperty));
- this [name].PropertyValue = configProperty.PropertyValue;
- }
- internal ConfigProperty? GetHardCodedProperty (string name)
- {
- ConfigProperty? configProperty = ConfigurationManager.GetHardCodedConfigPropertiesByScope (typeof (T).Name)!
- .FirstOrDefault (hardCodedKeyValuePair => hardCodedKeyValuePair.Key == name)
- .Value;
- if (configProperty is null)
- {
- return null;
- }
- var copy = ConfigProperty.CreateCopy (configProperty);
- copy.PropertyValue = configProperty.PropertyValue;
- return copy;
- }
- internal ConfigProperty GetUninitializedProperty (string name)
- {
- ConfigProperty? configProperty = ConfigurationManager.GetUninitializedConfigPropertiesByScope (typeof (T).Name)!
- .FirstOrDefault (hardCodedKeyValuePair => hardCodedKeyValuePair.Key == name)
- .Value;
- if (configProperty is null)
- {
- throw new InvalidOperationException ($@"{name} is not a ConfigProperty.");
- }
- var copy = ConfigProperty.CreateCopy (configProperty);
- copy.PropertyValue = configProperty.PropertyValue;
- return copy;
- }
- /// <summary>
- /// INTERNAL: Updates the values of the properties of this scope to their corresponding static
- /// <see cref="ConfigurationPropertyAttribute"/> properties.
- /// </summary>
- [RequiresDynamicCode ("Uses reflection to retrieve property values")]
- internal void UpdateToCurrentValues ()
- {
- foreach (KeyValuePair<string, ConfigProperty> validProperties in this.Where (cp => cp.Value.PropertyInfo is { }))
- {
- validProperties.Value.UpdateToCurrentValue ();
- }
- }
- /// <summary>
- /// INTERNAL: Updates the values of all properties of this scope to their corresponding hard-coded original values.
- /// </summary>
- internal void LoadHardCodedDefaults ()
- {
- foreach (KeyValuePair<string, ConfigProperty> hardCodedKeyValuePair in ConfigurationManager.GetHardCodedConfigPropertiesByScope (typeof (T).Name)!)
- {
- var copy = ConfigProperty.CreateCopy (hardCodedKeyValuePair.Value);
- TryAdd (hardCodedKeyValuePair.Key, copy);
- this [hardCodedKeyValuePair.Key].PropertyValue = hardCodedKeyValuePair.Value.PropertyValue;
- }
- }
- /// <summary>
- /// INTERNAL: Updates this scope with the values in <paramref name="scope"/> using a deep clone.
- /// </summary>
- /// <param name="scope"></param>
- /// <returns>The updated scope (this).</returns>
- [RequiresUnreferencedCode ("Calls Terminal.Gui.ConfigProperty.UpdateFrom(Object)")]
- [RequiresDynamicCode ("Calls Terminal.Gui.ConfigProperty.UpdateFrom(Object)")]
- internal Scope<T>? UpdateFrom (Scope<T> scope)
- {
- foreach (KeyValuePair<string, ConfigProperty> prop in scope)
- {
- if (!prop.Value.HasValue)
- {
- continue;
- }
- if (!ContainsKey (prop.Key))
- {
- if (!prop.Value.HasValue)
- {
- continue;
- }
- // Add an empty (HasValue = false) property to this scope
- var copy = ConfigProperty.CreateCopy (prop.Value);
- copy.PropertyValue = prop.Value.PropertyValue;
- TryAdd (prop.Key, copy);
- }
- // Update the property value
- this [prop.Key].UpdateFrom (prop.Value.PropertyValue);
- }
- return this;
- }
- /// <summary>
- /// INTERNAL: Applies the values of the properties of this scope to their corresponding
- /// <see cref="ConfigurationPropertyAttribute"/> properties.
- /// </summary>
- /// <returns><see langword="true"/> if one or more property value was applied; <see langword="false"/> otherwise.</returns>
- [RequiresDynamicCode ("Uses reflection to get and set property values")]
- [RequiresUnreferencedCode ("Calls Terminal.Gui.DeepCloner.DeepClone<T>(T)")]
- internal bool Apply ()
- {
- if (!ConfigurationManager.IsEnabled)
- {
- Logging.Warning ("Apply called when CM is not Enabled. This should only be done from unit tests where side-effects are managed.");
- }
- var set = false;
- foreach (KeyValuePair<string, ConfigProperty> propWithValue in this.Where (t => t.Value.HasValue))
- {
- if (propWithValue.Value.PropertyInfo != null)
- {
- object? currentValue = propWithValue.Value.PropertyInfo.GetValue (null);
- object? newValue = null;
- // QUESTION: Should we avoid setting if currentValue == newValue?
- if (propWithValue.Value.PropertyValue is Scope<T> scopeSource && currentValue is Scope<T> scopeDest)
- {
- newValue = scopeDest.UpdateFrom (scopeSource);
- }
- else
- {
- // Use DeepCloner to create a deep copy of the property value
- newValue = DeepCloner.DeepClone (propWithValue.Value.PropertyValue);
- }
- // Logging.Debug($"{propWithValue.Key}: {currentValue} -> {newValue}");
- Debug.Assert (!propWithValue.Value.Immutable);
- propWithValue.Value.PropertyInfo.SetValue (null, newValue);
- set = true;
- }
- }
- return set;
- }
- }
|