using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using JD.Collections; using JD.Kernel.Structs; using JD.LogService; using JD.Reflection.MefImplementations.Hosting.Sorting; using JD.TeaBoxAddinSystem.Addins.Description; using JD.TeaBoxAddinSystem.Addins.Description.Conditions; namespace JD.TeaBoxAddinSystem.Addins.Internal { /// /// A node in the extension tree. /// public class TreeNode : IEnumerable { #region Inner Types private class TreeNodeCollection : Collection { #region Inner Types private class TreeNodeTopologicalSort : TopologicalSort { #region Constructor public TreeNodeTopologicalSort(IEnumerable originalList) : base(originalList) { } #endregion #region Methods protected override bool TryGetKeyForItem(TreeNode item, out string key) { System.Diagnostics.Debug.Assert(item != null); key = item.Id; return true; } protected override string GetDisplayNameForItem(TreeNode item) { return item.Id; } protected override IEnumerable GetItemsAfterForItem(TreeNode item) { System.Diagnostics.Debug.Assert(item != null); if (item.m_nodesAfter != null) { return item.m_nodesAfter; } return new string[0]; } protected override IEnumerable GetItemsBeforeForItem(TreeNode item) { System.Diagnostics.Debug.Assert(item != null); if (item.m_nodesBefore != null) { return item.m_nodesBefore; } return new string[0]; } private static bool IsAll(IEnumerable nodes) { if (nodes == null) { return false; } const string AllString = "*"; return nodes.Any(node => node == AllString); } protected override TopologicalSortItemPositioningType GetItemPositioningType(TreeNode item) { System.Diagnostics.Debug.Assert(item != null); bool beforeAll = IsAll(item.m_nodesAfter); bool afterAll = IsAll(item.m_nodesBefore); if (afterAll && beforeAll) { Log(new Error(LogCategory.Warning, "Cannot have both insertafter and insertbefore set to '*'") { Origin = item.FileLocation }); return TopologicalSortItemPositioningType.AfterAll; } if (afterAll) { return TopologicalSortItemPositioningType.AfterAll; } if (beforeAll) { return TopologicalSortItemPositioningType.BeforeAll; } return base.GetItemPositioningType(item); } protected override void Log(LogEvent @event) { AddinManager.Log(@event); } protected override FileLocation GetFileLocation(TreeNode item) { return item.FileLocation; } #endregion } #endregion #region Variables private readonly TreeNode m_parent; private bool m_dirty = true; // Dirty flag. private readonly object m_lock = new object(); #endregion #region Constructor public TreeNodeCollection(TreeNode parent) { System.Diagnostics.Debug.Assert(parent != null); m_parent = parent; } #endregion #region Methods private void SetDirty() { m_dirty = true; } internal void Sort() { if (m_dirty) { lock (m_lock) { if (m_dirty) { List originalList = new List(this); base.ClearItems(); foreach (TreeNode node in new TreeNodeTopologicalSort(originalList).Sort()) { base.InsertItem(Count, node); } m_dirty = false; } } } } protected override void InsertItem(int index, TreeNode item) { item.Parent = m_parent; base.InsertItem(index, item); SetDirty(); } protected override void SetItem(int index, TreeNode item) { item.Parent = m_parent; base.SetItem(index, item); SetDirty(); } protected override void RemoveItem(int index) { base.RemoveItem(index); SetDirty(); } protected override void ClearItems() { base.ClearItems(); SetDirty(); } #endregion } /// /// Helper class, used for tests. /// public class TreeNodeBuilder { #region Variables private readonly TreeNode m_treeNode; #endregion #region Constructors /// /// Constructor. /// public TreeNodeBuilder() { m_treeNode = new TreeNode(); } /// /// Constructor. /// /// public TreeNodeBuilder(string id) { m_treeNode = new TreeNode(id); } #endregion #region Properties /// /// TreeNode. /// public TreeNode TreeNode { get { return m_treeNode; } } /// /// Child Nodes. /// public IList ChildNodes { get { return TreeNode.ChildNodesInternal; } } #endregion } #endregion #region Variables /// /// Prefix used to auto-generate ids for tree nodes. /// internal const string AutoIdPrefix = "__auto_node_id__"; private static int m_autoIdGenerator; private static readonly ExtensionNode m_dummyNode = new ExtensionNode(); private readonly string m_id; private readonly TreeNodeCollection m_childNodes; private TreeNode m_parent; private ExtensionNodeDescription m_extensionNodeDescription; private IExtensionTypeSetContainer m_allowedTypes; private bool? m_isValid; // Whether the conditions evaluated on this node are all true. // Only valid if there is an extension node description for this node. // Used for sorting private readonly List m_nodesBefore = new List(); private readonly List m_nodesAfter = new List(); private ExtensionNode m_extensionNode; #endregion #region Constructors /// /// Constructor. /// public TreeNode() { m_id = string.Empty; m_childNodes = new TreeNodeCollection(this); } /// /// Constructor. /// /// public TreeNode(string id) { if (string.IsNullOrEmpty(id)) { id = GenerateAutoId(); } else if (id.IndexOfAny(ExtensionPoint.GetPathSeparators()) != -1) { throw new ArgumentException("Id cannot contain path separator chars."); } m_id = id; m_childNodes = new TreeNodeCollection(this); } #endregion #region Properties /// /// Id of the node. /// public string Id { get { return m_id; } } /// /// Gets whether the of this tree node was defined by the user or was automatically generated. /// public bool HasIdDefined { get { return !m_id.StartsWith(AutoIdPrefix); } } /// /// Child Nodes. /// public IList ChildNodes { get { // Make sure the nodes are sorted m_childNodes.Sort(); return new ReadOnlyCollection(m_childNodes); } } internal IList ChildNodesInternal { get { return m_childNodes; } } internal FileLocation FileLocation { get { if (ExtensionNodeDescription != null) { return ExtensionNodeDescription.FileLocation.GetValueOrDefault(); } else { ExtensionPoint extensionPoint = ExtensionPoint; if (extensionPoint != null) { return extensionPoint.FileLocation.GetValueOrDefault(); } else { return FileLocation.Empty; } } } } /// /// Gets a child tree node by its index. /// /// /// The node at the index /// Thrown when the index is not in the node bounds. public TreeNode this[int index] { get { return ChildNodesInternal[index]; } } /// /// Gets a child tree node by its id. /// /// /// The node if existing, null otherwise public TreeNode this[string id] { get { return GetNode(ExtensionPoint.NormalizePath(id), false); } } /// /// Parent Tree node, if any. /// public TreeNode Parent { get { return m_parent; } internal set { System.Diagnostics.Debug.Assert(m_parent == null || value == null); m_parent = value; } } /// /// Full path of the tree node. /// public string FullPath { get { int num = 0; TreeNode node = this; while (node != null) { if (!string.IsNullOrEmpty(node.Id)) { num++; } node = node.Parent; } string[] ids = new string[num]; node = this; while (node != null) { if (!string.IsNullOrEmpty(node.Id)) { ids[--num] = node.Id; } node = node.Parent; } return string.Join(ExtensionPoint.PathSeparator.ToString(), ids); } } /// /// Extension Point. Mutually exclusive with . /// public ExtensionPoint ExtensionPoint { get { return AllowedTypes as ExtensionPoint; } } /// /// Extension node description. Mutually exclusive with . /// public ExtensionNodeDescription ExtensionNodeDescription { get { return m_extensionNodeDescription; } internal set { System.Diagnostics.Debug.Assert(m_extensionNodeDescription == null ^ value == null); System.Diagnostics.Debug.Assert(ExtensionPoint == null || value == null); m_extensionNodeDescription = value; } } internal IExtensionTypeSetContainer AllowedTypes { get { return m_allowedTypes; } set { System.Diagnostics.Debug.Assert(m_allowedTypes == null ^ value == null); m_allowedTypes = value; } } /// /// Gets the extension node corresponding to this tree node. Creates it if necessary. /// /// /// Returns null if a condition on this node evaluates to false. /// public ExtensionNode ExtensionNode { get { if (!IsValid) { return null; } if (m_extensionNode == null) { if (m_extensionNodeDescription != null) { // Semi-hack System.Diagnostics.Debug.Assert(AllowedTypes is ExtensionType); m_extensionNode = CreateExtensionNode(m_extensionNodeDescription, (ExtensionType)AllowedTypes); if (m_extensionNode == null) { m_extensionNode = m_dummyNode; } else { ApplyAllConditionTarget(); } } else if (ExtensionPoint != null) { // Create a dummy node that represents the extension point. var tmp = new ExtensionNode(); tmp.Initialize(this, ExtensionPoint.Module, null, null); m_extensionNode = tmp; } } if (m_extensionNode == m_dummyNode) { return null; } return m_extensionNode; } } /// /// Whether the on this tree node has been loaded. /// public bool IsLoaded { get { return (m_extensionNode != null); } } /// /// Gets whether the conditions on this node evaluate to true. /// private bool IsValid { get { // Check parent if (Parent != null && !Parent.IsValid) { return false; } if (!m_isValid.HasValue) { m_isValid = ComputeIsValid(null); } System.Diagnostics.Debug.Assert(m_isValid.HasValue); return m_isValid.Value; } } #endregion #region Methods internal void Clear() { ChildNodesInternal.Clear(); m_extensionNodeDescription = null; m_allowedTypes = null; m_nodesBefore.Clear(); m_nodesAfter.Clear(); m_extensionNode = null; } internal void LoadExtension(Extension extension) { System.Diagnostics.Debug.Assert(extension != null); if (LoadExtensionNodeDescriptions(extension.ExtensionNodes)) { // Tell the extension node if it is created OnChanged(); } } private bool LoadExtensionNodeDescriptions(IEnumerable nodes) { System.Diagnostics.Debug.Assert(nodes != null); TreeNode lastNode = null; bool changed = false; foreach (ExtensionNodeDescription description in nodes) { TreeNode currentNode = LoadExtensionNodeDescription(description); if (currentNode != null) { changed = true; if (lastNode != null) { currentNode.m_nodesBefore.Add(lastNode.Id); } lastNode = currentNode; } } return changed; } private TreeNode LoadExtensionNodeDescription(ExtensionNodeDescription description) { // Make sure this node type is valid here ExtensionType type = AddinManager.SessionService.FindExtensionType(AllowedTypes.ExtensionTypeSet, description.Name); if (type == null) { description.LogWarning("Cannot load extension node {0} because the type {1} is not valid at path {2}", description.DisplayId, description.Name, FullPath); return null; } TreeNode childNode = new TreeNode(description.Id); childNode.ExtensionNodeDescription = description; childNode.AllowedTypes = type; // Set insert before and insert after const char InsertionSeparator = ','; if (!string.IsNullOrEmpty(description.InsertAfter)) { childNode.m_nodesBefore.AddRange(description.InsertAfter.Split(new char[] { InsertionSeparator }, StringSplitOptions.RemoveEmptyEntries)); } if (!string.IsNullOrEmpty(description.InsertBefore)) { childNode.m_nodesAfter.AddRange(description.InsertBefore.Split(new char[] { InsertionSeparator }, StringSplitOptions.RemoveEmptyEntries)); } // Register conditions AddinManager.SessionService.ConditionManager.RegisterTreeNode(childNode); ChildNodesInternal.Add(childNode); // Load child nodes childNode.LoadExtensionNodeDescriptions(description.ChildNodes); return childNode; } internal void UnloadExtension(Extension extension) { System.Diagnostics.Debug.Assert(extension != null); bool changed = false; foreach (ExtensionNodeDescription description in extension.ExtensionNodes) { changed |= UnloadExtensionNodeDescription(description); } if (changed) { // Tell the extension node if it is created OnChanged(); } } private bool UnloadExtensionNodeDescription(ExtensionNodeDescription description) { bool changed = false; foreach (TreeNode childNode in new List(m_childNodes)) { // No assert here, extension node is not necessarily loaded because of type errors, etc. if (childNode.ExtensionNodeDescription == description) { AddinManager.SessionService.ConditionManager.UnregisterTreeNode(childNode); foreach (ExtensionNodeDescription childDescription in description.ChildNodes) { childNode.UnloadExtensionNodeDescription(childDescription); } ChildNodesInternal.Remove(childNode); changed = true; } } return changed; } internal TreeNode GetNode(string id, bool createIfNotFound) { System.Diagnostics.Debug.Assert(ExtensionPoint.NormalizePath(id) == id, "Path must be normalized"); TreeNode currentNode = this; if (!string.IsNullOrEmpty(id)) { string[] pathTokens = id.Split(ExtensionPoint.GetPathSeparators()); foreach (string token in pathTokens) { TreeNode childNode = currentNode.GetNodeInternal(token); if (childNode == null) { if (createIfNotFound) { childNode = new TreeNode(token); currentNode.ChildNodesInternal.Add(childNode); } else { currentNode = null; break; } } System.Diagnostics.Debug.Assert(childNode != null); currentNode = childNode; } } return currentNode; } private TreeNode GetNodeInternal(string id) { foreach (TreeNode node in this) { if (id == node.Id) { return node; } } return null; } private ExtensionNode CreateExtensionNode(ExtensionNodeDescription description, ExtensionType type) { ExtensionNode node = Node.CreateNode("extension", type); if (node == null || !node.Initialize(this, description.Module, description, type)) { return null; } return node; } private bool ComputeIsValid(string target) { bool isTargetEmpty = string.IsNullOrEmpty(target); if (ExtensionNodeDescription != null) { // Check conditions foreach (ConditionInfo conditionInfo in ExtensionNodeDescription.Conditions) { if ((isTargetEmpty && string.IsNullOrEmpty(conditionInfo.Target)) || string.Equals(target, conditionInfo.Target)) { if (!conditionInfo.Condition.Evaluate()) { return false; } } } } return true; } internal void OnChanged() { // Notify our extension node if (IsLoaded) { ExtensionNode.NotifyChildNodesChanged(new ExtensionNodeChangedEventArgs(this)); } } internal void OnConditionChanged() { m_isValid = null; // Reset m_isValid so that it will be recomputed next time. } private void ApplyAllConditionTarget() { if (ExtensionNodeDescription != null && IsLoaded) { List doneTargets = new List(); foreach (ConditionInfo conditionInfo in ExtensionNodeDescription.Conditions) { if (!string.IsNullOrEmpty(conditionInfo.Target) && !doneTargets.Contains(conditionInfo.Target)) { ApplyCondition(conditionInfo.Target); doneTargets.Add(conditionInfo.Target); } } } } internal void ApplyCondition(string target) { System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(target)); if (IsLoaded && ExtensionNode.Type.ConditionAttributes.Contains(target)) { StringPropertyDescriptor conditionalTargetInfo = ExtensionNode.Type.ConditionAttributes[target]; bool result = ComputeIsValid(target); try { conditionalTargetInfo.MemberInfo.SetValue(ExtensionNode, result); } catch (System.Reflection.TargetInvocationException e) { AddinManager.Log(new Error(LogCategory.Error, "Unexpected exception while setting conditional target {0} on node of type {1}: {2}", target, ExtensionNode.GetType().FullName, e.InnerException.Message)); } } } private static string GenerateAutoId() { return AutoIdPrefix + m_autoIdGenerator++; } /// /// Overriden. /// /// public override string ToString() { return $"[TreeNode: Id={Id}]"; } #endregion #region IEnumerable Members /// /// Get enumerator. /// /// public IEnumerator GetEnumerator() { return ChildNodes.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } }