// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using System; using System.Collections.Generic; using System.Linq; using System.Xml.Serialization; using System.Reflection; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Collections; using System.IO; namespace OpenTap { /// /// with this attribute belong to a group with a specified name. /// They can be marked as profile groups, enabling selectable profiles for settings in that group. /// [AttributeUsage(AttributeTargets.Class)] public class SettingsGroupAttribute : Attribute { /// /// Name of the settings group. /// public string GroupName { get; } /// /// Specifies whether this settings group uses profiles. /// public bool Profile { get; } /// /// Constructor for /// /// The name of the settings group. /// If this settings group should use profiles. public SettingsGroupAttribute(string GroupName, bool Profile = true) { this.GroupName = GroupName; this.Profile = Profile; } } /// /// Attribute that determines if the settings list should be fixed. /// [AttributeUsage(AttributeTargets.Class)] public class FixedSettingsListAttribute : Attribute { } internal interface IComponentSettingsList : IList { /// Return the objects which have been removed but still alive (non-GC'd) resources. /// This requires using a list of WeakReferences. IResource[] GetRemovedAliveResources(); } /// /// Contains some extra functionality for the ComponentSettingsList. /// Created so that it is possible to know which (generic) ComponentSettingsList /// contains a given type. /// public static class ComponentSettingsList { static Dictionary typeHandlersCache; static Dictionary typeHandlers { get { if (typeHandlersCache == null) { typeHandlersCache = new Dictionary(); foreach (Type settingsType in PluginManager.GetPlugins(typeof(ComponentSettings))) { Type baseType = settingsType; while (baseType != typeof(object)) { baseType = baseType.BaseType; if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(ComponentSettingsList<,>)) { Type[] types = settingsType.BaseType.GetGenericArguments(); if (types.Length == 2) { typeHandlersCache[types[1]] = types[0]; break; } } } } } return typeHandlersCache; } } /// /// Gets the GetCurrent method for the container for type T. /// Null if no such container. /// /// /// static PropertyInfo getGetCurrentMethodForContainer(Type T) { return getGetCurrentMethodsForContainer(T).FirstOrDefault(); } static IEnumerable getGetCurrentMethodsForContainer(Type T) { if (T == null) throw new ArgumentNullException("T"); foreach (Type key in typeHandlers.Keys) { if (T.DescendsTo(key)) { Type compSetType = typeHandlers[key]; if (GetCurrentProp(compSetType) is { } prop) yield return prop; } } } private static PropertyInfo GetCurrentProp(Type compSetType) { return compSetType.GetProperty("Current", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); } private static IList GetCurrent(Type compSetType) { if (GetCurrentProp(compSetType) is {} prop) return (IList)prop.GetValue(null, null); return null; } /// /// Finds a ComponentSettingsList containing instances of T. /// /// /// A List of type T. Null if no ComponentSettingsList exists containing T. public static IList GetContainer(Type T) { var m = getGetCurrentMethodForContainer(T); if (m == null) return null; return (IList)m.GetValue(null, null); } internal static IEnumerable GetResourceContainers() { foreach (Type key in typeHandlers.Keys) { if (!key.DescendsTo(typeof(IResource))) continue; Type compSetType = typeHandlers[key]; if (GetCurrent(compSetType) is IList lst) yield return lst; } } /// /// For checking if there is a ComponentSettings for T without evaluating GetCurrent. /// /// /// internal static bool HasContainer(Type T) { return getGetCurrentMethodForContainer(T) != null; } /// /// Gets the ComponentSettings list for T and filters the instances that are not T. /// /// public static IList GetItems() { IList items = GetContainer(typeof(T)); if (items == null) return new List(); return items.OfType().ToList(); } /// /// (non-generic) Gets the ComponentSettings list for T and filters the instances that are not T. /// /// /// public static IList GetItems(Type T) { var container = GetContainer(T); if (container == null) return new List(); return container.Cast().Where(v => v.GetType().DescendsTo(T)).ToList(); } } /// /// ComponentSettingsList is a collection of objects. This is the case for DutSettings, for instance. /// Uses IObservableCollection so that changes can be monitored. /// /// /// public abstract class ComponentSettingsList : ComponentSettings, INotifyCollectionChanged, IList, IList, IComponentSettingsList where DerivedType : ComponentSettingsList { readonly ObservableCollection list; readonly IList ilist; // Keep track of all the still living, but previously touched objects. // this is only used if ContainedType is a resource type. private readonly WeakHashSet touchedResources = new WeakHashSet(); /// /// Gets the first or default instance in the component settings list. /// /// /// public T GetDefault() where T : ContainedType { return GetDefaultOf(); } /// /// Static Get first or default instance in the component settings list. (uses GetCurrent) /// /// /// public static T GetDefaultOf() where T : ContainedType => GetCurrent().OfType().FirstOrDefault(); /// /// Initializes the list. /// public ComponentSettingsList() { list = new ObservableCollection(); list.CollectionChanged += OnCollectionChanged; ilist = list; } IResource[] IComponentSettingsList.GetRemovedAliveResources() { return touchedResources.GetElements().Where(x => Contains(x) == false).ToArray(); } /// This gets invoked when the collection is changed. internal protected virtual void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { var _newItem = e.NewItems.Cast().First(); if (_newItem is IResource newItem) { this.touchedResources.Add(newItem); var sameName = this.FirstOrDefault(itm => (itm as IResource).Name == newItem.Name && (itm as IResource) != newItem); int number = 0; while (sameName != null) { number++; sameName = this.FirstOrDefault(itm => (itm as IResource).Name == newItem.Name + number && (itm as IResource) != newItem); } if (number > 0) { newItem.Name += number; } } } CollectionChanged?.Invoke(sender, e); OnPropertyChanged(nameof(Count)); } /// /// Adds an element to the collection. /// /// public void Add(ContainedType item) { list.Add(item); } /// Adds a number of elements to the collection. /// The items to add to the list. public void AddRange(IEnumerable items) { foreach (var item in items) Add(item); } /// /// Removes all elements from the collection. /// public void Clear() { list.Clear(); } /// /// Determines if the collection contains the specified element. /// /// /// public bool Contains(ContainedType item) { return list.Contains(item); } /// /// Copies the collection to a compatible array. /// /// /// public void CopyTo(ContainedType[] array, int arrayIndex) { list.CopyTo(array, arrayIndex); } /// /// Gets the number of elements in the collection. /// public int Count { get { return list.Count; } } /// /// Determines if the collection is read only. /// public bool IsReadOnly { get { return false; } } /// /// Removes the first occurrence of a specified element from the collection. /// /// /// public bool Remove(ContainedType item) { return list.Remove(item); } /// /// Returns an that iterates through the collection. /// /// public IEnumerator GetEnumerator() { return list.GetEnumerator(); } /// /// Returns an that iterates through the collection. /// /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Returns the index of the first occurrences of a specified element in the collection. /// /// /// public int IndexOf(ContainedType item) { return list.IndexOf(item); } /// /// Insert an element into the collection at the specified index. /// /// /// public void Insert(int index, ContainedType item) { list.Insert(index, item); } /// /// Removes an element at a specified index. /// /// public void RemoveAt(int index) { list.RemoveAt(index); } /// /// List interface /// /// /// [XmlIgnore] public ContainedType this[int index] { get { return list[index]; } set { list[index] = value; } } /// /// Invoked when collection is changed. /// public event NotifyCollectionChangedEventHandler CollectionChanged; /// /// Adds an element to the collection. /// /// /// public int Add(object value) { return ilist.Add(value); } /// /// Determines if the collection contains the specified element. /// /// /// public bool Contains(object value) { return ilist.Contains(value); } /// /// Returns the index of the first occurrences of a specified element in the collection. /// /// /// public int IndexOf(object value) { return ilist.IndexOf(value); } /// /// Insert an element into the collection at the specified index. /// /// /// public void Insert(int index, object value) { ilist.Insert(index, value); } /// /// Determines if the collection is fixed size. /// public bool IsFixedSize { get { return ilist.IsFixedSize; } } /// /// Removes the first occurrence of a specified element from the collection. /// /// public void Remove(object value) { ilist.Remove(value); } /// /// List interface /// /// /// object IList.this[int index] { get { return ilist[index]; } set { ilist[index] = value; } } /// /// Copies the collection to a compatible array. /// /// /// public void CopyTo(Array array, int index) { ilist.CopyTo(array, index); } /// /// Determines if the collection is synchronized. /// public bool IsSynchronized { get { return ilist.IsSynchronized; } } /// /// Gets an object that can be used to synchronize access the collection. /// public object SyncRoot { get { return ilist.SyncRoot; } } } /// /// It is recommended to inherit from this class when implementing component settings. /// This class uses a recurrent template pattern to exhibit a kind of "static reflection". /// /// /// It is also possible implement a component setting by inherriting from the non-generic /// or it is just not recommended. /// /// The inheriting class. public abstract class ComponentSettings : ComponentSettings where T : ComponentSettings { /// /// Gets the current setting of a specific type. /// /// internal static T GetCurrent() => GetCurrent(); /// /// Get the currently loaded ComponentSettings instance for this class. /// public static T Current => GetCurrent(); internal static T CurrentFromCache => (T)GetCurrentFromCache(typeof(T)); } /// /// Specifies the ComponentSettings class to be a OpenTAP plugin. /// /// /// It is recommended to inherit from when possible. /// [Display("Component Settings")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public interface IComponentSettings : ITapPlugin { } /// /// An abstract class to implement general settings for a component. /// This class provides methods to load and save the settings to/from an XML file using TapSerializer. /// /// /// It is recommended to inherit from when possible. /// public abstract class ComponentSettings : ValidatingObject, IComponentSettings { internal XmlError[] loadErrors; string groupName; /// Settings group of this settings class. public string GroupName { get { if (groupName != null) return groupName; var settingsGroup = GetType().GetAttribute(); groupName = settingsGroup == null ? "" : settingsGroup.GroupName; return groupName; } } internal bool profile { get { var settingsGroup = GetType().GetAttribute(); if (settingsGroup == null) return false; return settingsGroup.Profile; } } /// /// Invokes when the cache for this settings item is invalidated for this item. /// The way to handle it is usually to fetch the new instance using ComponentSettings.GetCurrent(sender.GetType()). /// public event EventHandler CacheInvalidated; /// /// Where settings files are located. /// Usually this is at "[Executable location]\Settings", but it can be set to different locations. /// Setting this will invalidate loaded settings. /// public static string SettingsDirectoryRoot { get => context.SettingsDirectoryRoot; set => context.SettingsDirectoryRoot = value; } /// The directory where the settings are loaded from / saved to. /// Name of the settings group. /// If the settings group uses profiles, we load the default profile. public static string GetSettingsDirectory(string groupName, bool isProfile = true) => context.GetSettingsDirectory(groupName, isProfile); /// /// Ensures that the Settings directory exists and that the specified groupName sub directory exists. /// This might throw an exception if the settings directory was configured to something invalid. Like 'AUX', 'NUL', .... /// /// Name of the settings group. /// Determines if the settings group uses profiles. public static void EnsureSettingsDirectoryExists(string groupName, bool isProfile = true) => context.EnsureSettingsDirectoryExists(groupName, isProfile); /// Gets or sets if settings groups should be persisted between processes. [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static bool PersistSettingGroups = true; /// Sets the directory in which settings groups are loaded from / saved to. /// Name of the settings group. /// Name of the selected settings profile. public static void SetSettingsProfile(string groupName, string profileName) => context.SetSettingsProfile(groupName, profileName); /// Invalidates all loaded settings. Next time a ComponentSettings is accessed, it will be read from an XML file. internal static void InvalidateAllSettings() => context.InvalidateAllSettings(); static ComponentSettingsContext context => sessionContext.Value; static readonly SessionLocal sessionContext = new SessionLocal(new ComponentSettingsContext()); internal static void BeginSession() { var currentContext = context; var nextContext = currentContext.Clone(); nextContext.readOnlyContext = true; sessionContext.Value = nextContext; } /// /// Saves the settings held by this class to an XML file in the . /// public void Save() => context.Save(this); /// Saves all ComponentSettings objects that have been loaded. public static void SaveAllCurrentSettings() => context.SaveAllCurrentSettings(); /// /// Invalidates the cache of this type of component setting. /// public void Invalidate() => context.Invalidate(GetType()); /// /// Forces the reload of this type of component setting from the XML file the next time the setting is used. /// public void Reload() => context.Reload(); /// /// Called if a new ComponentSettings is instantiated and there are no corresponding settings XML. /// public virtual void Initialize() { } /// Gets the current file location where a ComponentSettings type is saved. /// Must be a ComponentSettings sub-class. /// public static string GetSaveFilePath(Type type) => context.GetSaveFilePath(type); /// /// Gets current settings for a specified component. This is either an instance of the settings class previously loaded, or a new instance loaded from the associated file. /// /// The type of the component settings requested (this type must be a descendant of ). /// Returns the loaded components settings. Null if it was not able to load the settings type. internal static T GetCurrent() where T : ComponentSettings => (T)GetCurrent(typeof(T)); /// /// Gets current settings for a specified component. This is either an instance of the settings class previously loaded, or a new instance loaded from the associated file. /// /// The type of the component settings requested (this type must be a descendant of ). /// Returns the loaded components settings. Null if it was not able to load the settings type. public static ComponentSettings GetCurrent(Type settingsType) => context.GetCurrent(settingsType); /// /// Sets current settings for a component setting based on a stream of the file contents of a ComponentSettings XML file. /// /// If the input stream is not valid XML /// The component settings stream to be set /// public static void SetCurrent(Stream xmlFileStream) => context.SetCurrent(xmlFileStream); /// /// Sets current settings for a component setting based on a stream of the file contents of a ComponentSettings XML file. /// /// The component settings stream to be set /// Any XML errors that occurred during deserialization /// public static void SetCurrent(Stream xmlFileStream, out IEnumerable errors) => context.SetCurrent(xmlFileStream, out errors); /// /// Gets current settings for a specified component. This is either an instance of the settings class previously loaded, or a new instance loaded from the associated file. /// /// The type of the component settings requested (this type must be a descendant of ). /// Returns the loaded components settings. Null if it was not able to load the settings type. public static ComponentSettings GetCurrent(ITypeData settingsType) { var td = settingsType.AsTypeData()?.Type; // in some rare cases, the type can be null // for example if the assembly could not be loaded(32/64bit incompatible), but it could be reflected. // in this case just return null. if (td == null) return null; return context.GetCurrent(td); } /// /// Gets current settings for a specified component from cache. /// /// The type of the component settings requested (this type must be a descendant of ). /// Returns the loaded components settings. Null if it was not able to load the settings type or if it was not cached. public static ComponentSettings GetCurrentFromCache(Type settingsType) => context.GetCurrentFromCache(settingsType); internal void InvokeInvalidate() { CacheInvalidated?.Invoke(this, EventArgs.Empty); } } }