JD2022-TU1/main/tools/framework/JD.TeaBoxAddinSystem/Addins/Internal/TreeNode.cs

885 lines
26 KiB
C#

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
{
/// <summary>
/// A node in the extension tree.
/// </summary>
public class TreeNode : IEnumerable<TreeNode>
{
#region Inner Types
private class TreeNodeCollection : Collection<TreeNode>
{
#region Inner Types
private class TreeNodeTopologicalSort : TopologicalSort<string, TreeNode>
{
#region Constructor
public TreeNodeTopologicalSort(IEnumerable<TreeNode> 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<string> 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<string> 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<string> 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<TreeNode> originalList = new List<TreeNode>(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
}
/// <summary>
/// Helper class, used for tests.
/// </summary>
public class TreeNodeBuilder
{
#region Variables
private readonly TreeNode m_treeNode;
#endregion
#region Constructors
/// <summary>
/// Constructor.
/// </summary>
public TreeNodeBuilder()
{
m_treeNode = new TreeNode();
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="id"></param>
public TreeNodeBuilder(string id)
{
m_treeNode = new TreeNode(id);
}
#endregion
#region Properties
/// <summary>
/// TreeNode.
/// </summary>
public TreeNode TreeNode
{
get
{
return m_treeNode;
}
}
/// <summary>
/// Child Nodes.
/// </summary>
public IList<TreeNode> ChildNodes
{
get
{
return TreeNode.ChildNodesInternal;
}
}
#endregion
}
#endregion
#region Variables
/// <summary>
/// Prefix used to auto-generate ids for tree nodes.
/// </summary>
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<string> m_nodesBefore = new List<string>();
private readonly List<string> m_nodesAfter = new List<string>();
private ExtensionNode m_extensionNode;
#endregion
#region Constructors
/// <summary>
/// Constructor.
/// </summary>
public TreeNode()
{
m_id = string.Empty;
m_childNodes = new TreeNodeCollection(this);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="id"></param>
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
/// <summary>
/// Id of the node.
/// </summary>
public string Id
{
get
{
return m_id;
}
}
/// <summary>
/// Gets whether the <see cref="Id"/> of this tree node was defined by the user or was automatically generated.
/// </summary>
public bool HasIdDefined
{
get
{
return !m_id.StartsWith(AutoIdPrefix);
}
}
/// <summary>
/// Child Nodes.
/// </summary>
public IList<TreeNode> ChildNodes
{
get
{
// Make sure the nodes are sorted
m_childNodes.Sort();
return new ReadOnlyCollection<TreeNode>(m_childNodes);
}
}
internal IList<TreeNode> 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;
}
}
}
}
/// <summary>
/// Gets a child tree node by its index.
/// </summary>
/// <param name="index"></param>
/// <returns>The node at the index</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the index is not in the node bounds.</exception>
public TreeNode this[int index]
{
get
{
return ChildNodesInternal[index];
}
}
/// <summary>
/// Gets a child tree node by its id.
/// </summary>
/// <param name="id"></param>
/// <returns>The node if existing, null otherwise</returns>
public TreeNode this[string id]
{
get
{
return GetNode(ExtensionPoint.NormalizePath(id), false);
}
}
/// <summary>
/// Parent Tree node, if any.
/// </summary>
public TreeNode Parent
{
get
{
return m_parent;
}
internal set
{
System.Diagnostics.Debug.Assert(m_parent == null || value == null);
m_parent = value;
}
}
/// <summary>
/// Full path of the tree node.
/// </summary>
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);
}
}
/// <summary>
/// Extension Point. Mutually exclusive with <see cref="ExtensionNodeDescription"/>.
/// </summary>
public ExtensionPoint ExtensionPoint
{
get
{
return AllowedTypes as ExtensionPoint;
}
}
/// <summary>
/// Extension node description. Mutually exclusive with <see cref="ExtensionPoint"/>.
/// </summary>
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;
}
}
/// <summary>
/// Gets the extension node corresponding to this tree node. Creates it if necessary.
/// </summary>
/// <remarks>
/// Returns null if a condition on this node evaluates to false.
/// </remarks>
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;
}
}
/// <summary>
/// Whether the <see cref="ExtensionNode"/> on this tree node has been loaded.
/// </summary>
public bool IsLoaded
{
get
{
return (m_extensionNode != null);
}
}
/// <summary>
/// Gets whether the conditions on this node evaluate to true.
/// </summary>
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<ExtensionNodeDescription> 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<TreeNode>(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<ExtensionNode>("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<string> doneTargets = new List<string>();
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++;
}
/// <summary>
/// Overriden.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"[TreeNode: Id={Id}]";
}
#endregion
#region IEnumerable<TreeNode> Members
/// <summary>
/// Get enumerator.
/// </summary>
/// <returns></returns>
public IEnumerator<TreeNode> GetEnumerator()
{
return ChildNodes.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}