Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/mono-addins.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLluis Sanchez <llsan@microsoft.com>2022-06-10 12:18:20 +0300
committerLluis Sanchez <llsan@microsoft.com>2022-09-13 20:38:41 +0300
commit448354374f80513a84a2b547b87c66ef7050dda6 (patch)
treeff115a7ac94bfb40072bb595b3689bc08810881b
parent10d055187ecf7df8145b426b3f9122a73b358b60 (diff)
Ongoing work to make mono.addins thread safe
-rw-r--r--Mono.Addins/Mono.Addins.Database/AddinDatabase.cs42
-rw-r--r--Mono.Addins/Mono.Addins.csproj2
-rw-r--r--Mono.Addins/Mono.Addins/AddinEngine.cs54
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionContext.cs608
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs117
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionNode.cs147
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionNodeList.cs31
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionTree.cs34
-rw-r--r--Mono.Addins/Mono.Addins/TreeNode.cs195
-rw-r--r--Mono.Addins/Mono.Addins/TreeNodeBuilder.cs177
-rw-r--r--Mono.Addins/Mono.Addins/TreeNodeCollection.cs86
11 files changed, 893 insertions, 600 deletions
diff --git a/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs b/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs
index 34f931f..e7baccb 100644
--- a/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs
+++ b/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs
@@ -493,8 +493,10 @@ namespace Mono.Addins.Database
Configuration.SetEnabled (id, true, ainfo.AddinInfo.EnabledByDefault, true);
SaveConfiguration ();
- if (addinEngine != null && addinEngine.IsInitialized)
- addinEngine.ActivateAddin (id);
+ if (addinEngine != null && addinEngine.IsInitialized) {
+ using var transaction = addinEngine.BeginTransaction ();
+ addinEngine.ActivateAddin (transaction, id);
+ }
}
public void DisableAddin (string domain, string id, bool exactVersionMatch = false, bool onlyForCurrentSession = false)
@@ -544,8 +546,10 @@ namespace Mono.Addins.Database
throw;
}
- if (addinEngine != null && addinEngine.IsInitialized)
- addinEngine.UnloadAddin (id);
+ if (addinEngine != null && addinEngine.IsInitialized) {
+ using var transaction = addinEngine.BeginTransaction ();
+ addinEngine.UnloadAddin (transaction, id);
+ }
}
public void UpdateEnabledStatus ()
@@ -1140,20 +1144,22 @@ namespace Mono.Addins.Database
foreach (Addin ainfo in GetInstalledAddins (domain, AddinSearchFlagsInternal.IncludeAddins)) {
newInstalled [ainfo.Id] = ainfo.Id;
}
-
- foreach (string aid in installed.Keys) {
- // Always try to unload, event if the add-in was not currently loaded.
- // Required since the add-ins has to be marked as 'disabled', to avoid
- // extensions from this add-in to be loaded
- if (!newInstalled.Contains (aid))
- addinEngine.UnloadAddin (aid);
- }
-
- foreach (string aid in newInstalled.Keys) {
- if (!installed.Contains (aid)) {
- Addin addin = addinEngine.Registry.GetAddin (aid);
- if (addin != null)
- addinEngine.ActivateAddin (aid);
+
+ using (var transaction = addinEngine.BeginTransaction ()) {
+ foreach (string aid in installed.Keys) {
+ // Always try to unload, event if the add-in was not currently loaded.
+ // Required since the add-ins has to be marked as 'disabled', to avoid
+ // extensions from this add-in to be loaded
+ if (!newInstalled.Contains (aid))
+ addinEngine.UnloadAddin (transaction, aid);
+ }
+
+ foreach (string aid in newInstalled.Keys) {
+ if (!installed.Contains (aid)) {
+ Addin addin = addinEngine.Registry.GetAddin (aid);
+ if (addin != null)
+ addinEngine.ActivateAddin (transaction, aid);
+ }
}
}
}
diff --git a/Mono.Addins/Mono.Addins.csproj b/Mono.Addins/Mono.Addins.csproj
index cfd7346..73bf194 100644
--- a/Mono.Addins/Mono.Addins.csproj
+++ b/Mono.Addins/Mono.Addins.csproj
@@ -15,10 +15,12 @@
<NoWarn>1574;1591</NoWarn>
<ConsolePause>False</ConsolePause>
<OutputPath>..\bin</OutputPath>
+ <LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
+ <PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Remove="CustomConditionAttribute.cs" />
diff --git a/Mono.Addins/Mono.Addins/AddinEngine.cs b/Mono.Addins/Mono.Addins/AddinEngine.cs
index 9b361a0..7603b3e 100644
--- a/Mono.Addins/Mono.Addins/AddinEngine.cs
+++ b/Mono.Addins/Mono.Addins/AddinEngine.cs
@@ -473,14 +473,14 @@ namespace Mono.Addins
return a;
}
- internal void ActivateAddin (string id)
+ internal void ActivateAddin (ExtensionContextTransaction transaction, string id)
{
- ActivateAddinExtensions (id);
+ ActivateAddinExtensions (transaction, id);
}
-
- internal void UnloadAddin (string id)
+
+ internal void UnloadAddin (ExtensionContextTransaction transaction, string id)
{
- RemoveAddinExtensions (id);
+ RemoveAddinExtensions (transaction, id);
RuntimeAddin addin = GetAddin (id);
if (addin != null) {
@@ -544,21 +544,31 @@ namespace Mono.Addins
if (statusMonitor != null)
statusMonitor.SetMessage ("Loading Addins");
-
- for (int n=0; n<addins.Count; n++) {
-
- if (statusMonitor != null)
- statusMonitor.SetProgress ((double) n / (double)addins.Count);
-
- Addin iad = addins [n];
- if (IsAddinLoaded (iad.Id))
- continue;
-
- if (statusMonitor != null)
- statusMonitor.SetMessage (string.Format(GettextCatalog.GetString("Loading {0} add-in"), iad.Id));
-
- if (!InsertAddin (statusMonitor, iad))
- return false;
+
+ if (addins.Count > 0) {
+ ExtensionContextTransaction transaction = null;
+ try {
+ for (int n = 0; n < addins.Count; n++) {
+
+ if (statusMonitor != null)
+ statusMonitor.SetProgress ((double)n / (double)addins.Count);
+
+ Addin iad = addins [n];
+ if (IsAddinLoaded (iad.Id))
+ continue;
+
+ if (statusMonitor != null)
+ statusMonitor.SetMessage (string.Format (GettextCatalog.GetString ("Loading {0} add-in"), iad.Id));
+
+ if (transaction == null)
+ transaction = BeginTransaction ();
+
+ if (!InsertAddin (transaction, statusMonitor, iad))
+ return false;
+ }
+ } finally {
+ transaction?.Dispose ();
+ }
}
return true;
}
@@ -580,7 +590,7 @@ namespace Mono.Addins
base.ResetCachedData ();
}
- bool InsertAddin (IProgressStatus statusMonitor, Addin iad)
+ bool InsertAddin (ExtensionContextTransaction transaction, IProgressStatus statusMonitor, Addin iad)
{
try {
RuntimeAddin runtimeAddin = new RuntimeAddin (this);
@@ -601,7 +611,7 @@ namespace Mono.Addins
RegisterNodeSets (iad.Id, description.ExtensionNodeSets);
foreach (ConditionTypeDescription cond in description.ConditionTypes)
- RegisterCondition (cond.Id, runtimeAddin, cond.TypeName);
+ RegisterCondition (transaction, cond.Id, runtimeAddin, cond.TypeName);
}
foreach (ExtensionPoint ep in description.ExtensionPoints)
diff --git a/Mono.Addins/Mono.Addins/ExtensionContext.cs b/Mono.Addins/Mono.Addins/ExtensionContext.cs
index 7b510a3..85de09c 100644
--- a/Mono.Addins/Mono.Addins/ExtensionContext.cs
+++ b/Mono.Addins/Mono.Addins/ExtensionContext.cs
@@ -30,31 +30,32 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Collections.Specialized;
+using System.Linq;
using Mono.Addins.Description;
namespace Mono.Addins
{
- /// <summary>
- /// An extension context.
- /// </summary>
- /// <remarks>
- /// Extension contexts can be used to query the extension tree
- /// using particular condition values. Extension points which
- /// declare the availability of a condition type can only be
- /// queryed using an extension context which provides an
- /// evaluator for that condition.
- /// </remarks>
- public class ExtensionContext
+ /// <summary>
+ /// An extension context.
+ /// </summary>
+ /// <remarks>
+ /// Extension contexts can be used to query the extension tree
+ /// using particular condition values. Extension points which
+ /// declare the availability of a condition type can only be
+ /// queryed using an extension context which provides an
+ /// evaluator for that condition.
+ /// </remarks>
+ public class ExtensionContext
{
internal object LocalLock = new object ();
- Hashtable conditionTypes = new Hashtable ();
- Hashtable conditionsToNodes = new Hashtable ();
+ ImmutableDictionary<string, ConditionInfo> conditionTypes = ImmutableDictionary<string, ConditionInfo>.Empty;
+ ImmutableDictionary<BaseCondition, ImmutableArray<TreeNode>> conditionsToNodes = ImmutableDictionary<BaseCondition, ImmutableArray<TreeNode>>.Empty;
List<WeakReference> childContexts;
ExtensionContext parentContext;
ExtensionTree tree;
- bool fireEvents = false;
List<string> runTimeEnabledAddins;
List<string> runTimeDisabledAddins;
@@ -73,7 +74,6 @@ namespace Mono.Addins
internal void Initialize (AddinEngine addinEngine)
{
- fireEvents = false;
tree = new ExtensionTree (addinEngine, this);
}
@@ -87,7 +87,7 @@ namespace Mono.Addins
internal void ClearContext ()
{
- conditionTypes.Clear ();
+ conditionTypes = conditionTypes.Clear ();
conditionsToNodes.Clear ();
childContexts = null;
parentContext = null;
@@ -108,14 +108,11 @@ namespace Mono.Addins
internal virtual void ResetCachedData ()
{
- tree.ResetCachedData ();
- if (childContexts != null) {
- foreach (WeakReference wref in childContexts) {
- ExtensionContext ctx = wref.Target as ExtensionContext;
- if (ctx != null)
- ctx.ResetCachedData ();
- }
- }
+ using (var transaction = BeginTransaction ())
+ tree.ResetCachedData (transaction);
+
+ foreach (var ctx in GetActiveChildContexes())
+ ctx.ResetCachedData ();
}
internal ExtensionContext CreateChildContext ()
@@ -150,13 +147,9 @@ namespace Mono.Addins
/// </remarks>
public void RegisterCondition (string id, ConditionType type)
{
+ using var transaction = BeginTransaction ();
type.Id = id;
- ConditionInfo info = CreateConditionInfo (id);
- ConditionType ot = info.CondType as ConditionType;
- if (ot != null)
- ot.Changed -= new EventHandler (OnConditionChanged);
- info.CondType = type;
- type.Changed += new EventHandler (OnConditionChanged);
+ CreateConditionInfo (transaction, id, type);
}
/// <summary>
@@ -174,63 +167,75 @@ namespace Mono.Addins
/// </remarks>
public void RegisterCondition (string id, Type type)
{
+ using var transaction = BeginTransaction ();
+
// Allows delayed creation of condition types
- ConditionInfo info = CreateConditionInfo (id);
- ConditionType ot = info.CondType as ConditionType;
- if (ot != null)
- ot.Changed -= new EventHandler (OnConditionChanged);
- info.CondType = type;
+ CreateConditionInfo (transaction, id, type);
}
- internal void RegisterCondition (string id, RuntimeAddin addin, string typeName)
+ void RegisterCondition (ExtensionContextTransaction transaction, string id, Type type)
{
// Allows delayed creation of condition types
- ConditionInfo info = CreateConditionInfo (id);
- ConditionType ot = info.CondType as ConditionType;
- if (ot != null)
- ot.Changed -= new EventHandler (OnConditionChanged);
- info.CondType = new ConditionTypeData {
+ CreateConditionInfo (transaction, id, type);
+ }
+
+ internal void RegisterCondition (ExtensionContextTransaction transaction, string id, RuntimeAddin addin, string typeName)
+ {
+ // Allows delayed creation of condition types
+ CreateConditionInfo (transaction, id, new ConditionTypeData {
TypeName = typeName,
Addin = addin
- };
+ });
}
- ConditionInfo CreateConditionInfo (string id)
+ ConditionInfo CreateConditionInfo (ExtensionContextTransaction transaction, string id, object conditionTypeObject)
{
- ConditionInfo info = conditionTypes [id] as ConditionInfo;
- if (info == null) {
+ if (!conditionTypes.TryGetValue (id, out var info)) {
info = new ConditionInfo ();
- conditionTypes [id] = info;
+ info.CondType = conditionTypeObject;
+ conditionTypes = conditionTypes.Add (id, info);
+ } else {
+ var oldType = info.CondType as ConditionType;
+ info.CondType = conditionTypeObject;
+ if (oldType != null)
+ oldType.Changed -= new EventHandler (OnConditionChanged);
}
+ if (conditionTypeObject is ConditionType conditionType)
+ conditionType.Changed += new EventHandler (OnConditionChanged);
return info;
}
- internal bool FireEvents {
- get { return fireEvents; }
- }
-
internal ConditionType GetCondition (string id)
{
- ConditionInfo info = (ConditionInfo) conditionTypes [id];
-
- if (info != null) {
+ if (conditionTypes.TryGetValue(id, out var info)) {
if (info.CondType is ConditionType condition) {
return condition;
}
else {
- // The condition was registered as a type, create an instance now
- Type type;
- if (info.CondType is ConditionTypeData data) {
- type = data.Addin.GetType (data.TypeName, true);
- } else
- type = info.CondType as Type;
-
- if (type != null) {
- var ct = (ConditionType)Activator.CreateInstance (type);
- ct.Id = id;
- ct.Changed += new EventHandler (OnConditionChanged);
- info.CondType = ct;
- return ct;
+ // The condition needs to be instantiated in a lock, to avoid duplicate
+ // creation if two threads are trying to get it
+
+ lock (LocalLock) {
+
+ // Check again from inside the lock (maybe another thread already created the condition)
+ if (info.CondType is ConditionType cond)
+ return cond;
+
+ // The condition was registered as a type, create an instance now
+
+ Type type;
+ if (info.CondType is ConditionTypeData data) {
+ type = data.Addin.GetType (data.TypeName, true);
+ } else
+ type = info.CondType as Type;
+
+ if (type != null) {
+ var ct = (ConditionType)Activator.CreateInstance (type);
+ ct.Id = id;
+ ct.Changed += new EventHandler (OnConditionChanged);
+ info.CondType = ct;
+ return ct;
+ }
}
}
}
@@ -241,46 +246,55 @@ namespace Mono.Addins
return null;
}
- internal void RegisterNodeCondition (TreeNode node, BaseCondition cond)
+ internal void BulkRegisterNodeConditions (ExtensionContextTransaction transaction, IEnumerable<(TreeNode Node, BaseCondition Condition)> nodeConditions)
{
- var list = (List<TreeNode>) conditionsToNodes [cond];
- if (list == null) {
- list = new List<TreeNode> ();
- conditionsToNodes [cond] = list;
- var conditionTypeIds = new List<string> ();
- cond.GetConditionTypes (conditionTypeIds);
-
- foreach (string cid in conditionTypeIds) {
-
- ConditionInfo info = CreateConditionInfo (cid);
- if (info.BoundConditions == null)
- info.BoundConditions = new List<BaseCondition> ();
-
- info.BoundConditions.Add (cond);
- }
+ var dictBuilder = ImmutableDictionary.CreateBuilder<BaseCondition, ImmutableArray<TreeNode>> ();
+
+ foreach (var group in nodeConditions.GroupBy (c => c.Condition)) {
+ var cond = group.Key;
+ ImmutableArray<TreeNode>.Builder listBuilder;
+ if (!conditionsToNodes.TryGetValue (group.Key, out var list)) {
+ listBuilder = ImmutableArray.CreateBuilder<TreeNode> ();
+
+ var conditionTypeIds = new List<string> ();
+ cond.GetConditionTypes (conditionTypeIds);
+
+ foreach (string cid in conditionTypeIds) {
+
+ ConditionInfo info = CreateConditionInfo (transaction, cid, null);
+ if (info.BoundConditions == null)
+ info.BoundConditions = new List<BaseCondition> ();
+
+ info.BoundConditions.Add (cond);
+ }
+ } else
+ listBuilder = list.ToBuilder();
+
+ listBuilder.AddRange (group.Select (item => item.Node));
+ dictBuilder [cond] = listBuilder.ToImmutable ();
}
- list.Add (node);
+ conditionsToNodes = dictBuilder.ToImmutable ();
}
-
- internal void UnregisterNodeCondition (TreeNode node, BaseCondition cond)
+
+ internal void BulkUnregisterNodeConditions (ExtensionContextTransaction transaction, IEnumerable<(TreeNode Node, BaseCondition Condition)> nodeConditions)
{
- var list = (List<TreeNode>) conditionsToNodes [cond];
- if (list == null)
- return;
-
- list.Remove (node);
- if (list.Count == 0) {
- conditionsToNodes.Remove (cond);
- var conditionTypeIds = new List<string> ();
- cond.GetConditionTypes (conditionTypeIds);
- foreach (string cid in conditionTypes.Keys) {
- ConditionInfo info = conditionTypes [cid] as ConditionInfo;
- if (info != null && info.BoundConditions != null)
- info.BoundConditions.Remove (cond);
- }
+ ImmutableDictionary<BaseCondition, ImmutableArray<TreeNode>>.Builder dictBuilder = null;
+
+ foreach (var group in nodeConditions.GroupBy (c => c.Condition)) {
+ var cond = group.Key;
+ if (!conditionsToNodes.TryGetValue (cond, out var list))
+ continue;
+
+ if (dictBuilder == null)
+ dictBuilder = conditionsToNodes.ToBuilder ();
+
+ list = list.RemoveRange (group.Select (item => item.Node));
+ dictBuilder [cond] = list;
}
+ if (dictBuilder != null)
+ conditionsToNodes = dictBuilder.ToImmutable ();
}
-
+
/// <summary>
/// Returns the extension node in a path
/// </summary>
@@ -388,7 +402,7 @@ namespace Mono.Addins
{
string path = AddinEngine.GetAutoTypeExtensionPoint (instanceType);
if (path == null)
- return new ExtensionNodeList (null);
+ return ExtensionNodeList.Empty;
return GetExtensionNodes (path, expectedNodeType);
}
@@ -434,7 +448,7 @@ namespace Mono.Addins
public ExtensionNodeList GetExtensionNodes (string path, Type expectedNodeType)
{
TreeNode node = GetNode (path);
- if (node == null || node.ExtensionNode == null)
+ if (node == null || !node.HasExtensionNode)
return ExtensionNodeList.Empty;
ExtensionNodeList list = node.ExtensionNode.ChildNodes;
@@ -772,57 +786,57 @@ namespace Mono.Addins
throw new InvalidOperationException ("Type '" + instanceType + "' not bound to an extension point.");
RemoveExtensionNodeHandler (path, handler);
}
+
+ internal ExtensionContextTransaction BeginTransaction ()
+ {
+ return new ExtensionContextTransaction (this);
+ }
void OnConditionChanged (object s, EventArgs a)
{
ConditionType cond = (ConditionType) s;
NotifyConditionChanged (cond);
}
-
+
internal void NotifyConditionChanged (ConditionType cond)
{
- try {
- fireEvents = true;
-
- ConditionInfo info = (ConditionInfo) conditionTypes [cond.Id];
- if (info != null && info.BoundConditions != null) {
- Hashtable parentsToNotify = new Hashtable ();
- foreach (BaseCondition c in info.BoundConditions) {
- var nodeList = (List<TreeNode>) conditionsToNodes [c];
- if (nodeList != null) {
- foreach (TreeNode node in nodeList)
- parentsToNotify [node.Parent] = null;
- }
- }
- foreach (TreeNode node in parentsToNotify.Keys) {
- if (node.NotifyChildrenChanged ())
- NotifyExtensionsChanged (new ExtensionEventArgs (node.GetPath ()));
+ HashSet<TreeNode> parentsToNotify = null;
+
+ if (conditionTypes.TryGetValue (cond.Id, out var info) && info.BoundConditions != null) {
+ parentsToNotify = new HashSet<TreeNode> ();
+ foreach (BaseCondition c in info.BoundConditions) {
+ if (conditionsToNodes.TryGetValue(c, out var nodeList)) {
+ parentsToNotify.UnionWith (nodeList.Select (node => node.Parent));
}
}
}
- finally {
- fireEvents = false;
+
+ if (parentsToNotify != null) {
+ foreach (TreeNode node in parentsToNotify) {
+ if (node.NotifyChildrenChanged ())
+ NotifyExtensionsChanged (new ExtensionEventArgs (node.GetPath ()));
+ }
}
- // Notify child contexts
- lock (conditionTypes) {
+ foreach (var ctx in GetActiveChildContexes ())
+ ctx.NotifyConditionChanged (cond);
+ }
+
+ ExtensionContext[] GetActiveChildContexes ()
+ {
+ // Collect a list of child contexts that are still referenced
+ lock (LocalLock) {
if (childContexts != null) {
CleanDisposedChildContexts ();
- foreach (WeakReference wref in childContexts) {
- ExtensionContext ctx = wref.Target as ExtensionContext;
- if (ctx != null)
- ctx.NotifyConditionChanged (cond);
- }
- }
+ return childContexts.Select (wref => wref.Target as ExtensionContext).Where (t => t != null).ToArray ();
+ } else
+ return Array.Empty<ExtensionContext> ();
}
}
-
+
internal void NotifyExtensionsChanged (ExtensionEventArgs args)
{
- if (!fireEvents)
- return;
-
if (ExtensionChanged != null)
ExtensionChanged (this, args);
}
@@ -831,16 +845,8 @@ namespace Mono.Addins
{
tree.NotifyAddinLoaded (ad, true);
- lock (conditionTypes) {
- if (childContexts != null) {
- CleanDisposedChildContexts ();
- foreach (WeakReference wref in childContexts) {
- ExtensionContext ctx = wref.Target as ExtensionContext;
- if (ctx != null)
- ctx.NotifyAddinLoaded (ad);
- }
- }
- }
+ foreach (var ctx in GetActiveChildContexes())
+ ctx.NotifyAddinLoaded (ad);
}
internal void CreateExtensionPoint (ExtensionPoint ep)
@@ -852,129 +858,92 @@ namespace Mono.Addins
}
}
- internal void ActivateAddinExtensions (string id)
+ internal void ActivateAddinExtensions (ExtensionContextTransaction transaction, string id)
{
// Looks for loaded extension points which are extended by the provided
// add-in, and adds the new nodes
- try {
- fireEvents = true;
-
- Addin addin = AddinEngine.Registry.GetAddin (id);
- if (addin == null) {
- AddinEngine.ReportError ("Required add-in not found", id, null, false);
- return;
- }
- // Take note that this add-in has been enabled at run-time
- // Needed because loaded add-in descriptions may not include this add-in.
- RegisterRuntimeEnabledAddin (id);
+ Addin addin = AddinEngine.Registry.GetAddin (id);
+ if (addin == null) {
+ AddinEngine.ReportError ("Required add-in not found", id, null, false);
+ return;
+ }
+ // Take note that this add-in has been enabled at run-time
+ // Needed because loaded add-in descriptions may not include this add-in.
+ RegisterRuntimeEnabledAddin (id);
- // Look for loaded extension points
- Hashtable eps = new Hashtable ();
- var newExtensions = new List<string> ();
- foreach (ModuleDescription mod in addin.Description.AllModules) {
- foreach (Extension ext in mod.Extensions) {
- if (!newExtensions.Contains (ext.Path))
- newExtensions.Add (ext.Path);
- ExtensionPoint ep = tree.FindLoadedExtensionPoint (ext.Path);
- if (ep != null && !eps.Contains (ep))
- eps.Add (ep, ep);
- }
+ // Look for loaded extension points
+ Hashtable eps = new Hashtable ();
+ foreach (ModuleDescription mod in addin.Description.AllModules) {
+ foreach (Extension ext in mod.Extensions) {
+ transaction.NotifyExtensionsChangedEvent (ext.Path);
+ ExtensionPoint ep = tree.FindLoadedExtensionPoint (ext.Path);
+ if (ep != null && !eps.Contains (ep))
+ eps.Add (ep, ep);
}
+ }
- // Add the new nodes
- var loadedNodes = new List<TreeNode> ();
- foreach (ExtensionPoint ep in eps.Keys) {
- ExtensionLoadData data = GetAddinExtensions (id, ep);
- if (data != null) {
- foreach (Extension ext in data.Extensions) {
- TreeNode node = GetNode (ext.Path);
- if (node != null && node.ExtensionNodeSet != null) {
- if (node.ChildrenLoaded)
- LoadModuleExtensionNodes (ext, data.AddinId, node.ExtensionNodeSet, loadedNodes);
+ // Add the new nodes
+ foreach (ExtensionPoint ep in eps.Keys) {
+ ExtensionLoadData data = GetAddinExtensions (id, ep);
+ if (data != null) {
+ foreach (Extension ext in data.Extensions) {
+ TreeNode node = GetNode (ext.Path);
+ if (node != null && node.ExtensionNodeSet != null) {
+ if (node.ChildrenLoaded) {
+ var builder = new TreeNodeBuilder (node);
+ LoadModuleExtensionNodes (transaction, builder, ext, data.AddinId);
+ builder.Build (transaction);
}
- else
- AddinEngine.ReportError ("Extension node not found or not extensible: " + ext.Path, id, null, false);
}
+ else
+ AddinEngine.ReportError ("Extension node not found or not extensible: " + ext.Path, id, null, false);
}
}
-
- // Call the OnAddinLoaded method on nodes, if the add-in is already loaded
- foreach (TreeNode nod in loadedNodes)
- nod.ExtensionNode.OnAddinLoaded ();
-
- // Global extension change event. Other events are fired by LoadModuleExtensionNodes.
- // The event is called for all extensions, even for those not loaded. This is for coherence,
- // although that something that it doesn't make much sense to do (subscribing the ExtensionChanged
- // event without first getting the list of nodes that may change).
- foreach (string newExt in newExtensions)
- NotifyExtensionsChanged (new ExtensionEventArgs (newExt));
- }
- finally {
- fireEvents = false;
}
+
// Do the same in child contexts
-
- lock (conditionTypes) {
- if (childContexts != null) {
- CleanDisposedChildContexts ();
- foreach (WeakReference wref in childContexts) {
- ExtensionContext ctx = wref.Target as ExtensionContext;
- if (ctx != null)
- ctx.ActivateAddinExtensions (id);
- }
- }
- }
+
+ foreach (var ctx in GetActiveChildContexes ())
+ ctx.ActivateAddinExtensions (transaction, id);
}
-
- internal void RemoveAddinExtensions (string id)
+
+ internal void RemoveAddinExtensions (ExtensionContextTransaction transaction, string id)
{
- try {
- // Registers this add-in as disabled, so from now on extension from this
- // add-in will be ignored
- RegisterRuntimeDisabledAddin (id);
-
- fireEvents = true;
+ // Registers this add-in as disabled, so from now on extension from this
+ // add-in will be ignored
+ RegisterRuntimeDisabledAddin (id);
- // This method removes all extension nodes added by the add-in
- // Get all nodes created by the addin
- List<TreeNode> list = new List<TreeNode> ();
- tree.FindAddinNodes (id, list);
-
- // Remove each node and notify the change
- foreach (TreeNode node in list) {
- if (node.ExtensionNode == null) {
- // It's an extension point. Just remove it, no notifications are needed
- node.Remove ();
- }
- else {
- node.ExtensionNode.OnAddinUnloaded ();
- node.Remove ();
+ // This method removes all extension nodes added by the add-in
+ // Get all nodes created by the addin
+ List<TreeNode> list = new List<TreeNode> ();
+ tree.FindAddinNodes (id, list);
+
+ // Remove each node and notify the change
+ foreach (TreeNode node in list) {
+ node.NotifyAddinUnloaded ();
+ node.Remove (transaction);
+ }
+
+ // Notify global extension point changes.
+ // The event is called for all extensions, even for those not loaded. This is for coherence,
+ // although that something that it doesn't make much sense to do (subscribing the ExtensionChanged
+ // event without first getting the list of nodes that may change).
+
+ // We get the runtime add-in because the add-in may already have been deleted from the registry
+ RuntimeAddin addin = AddinEngine.GetAddin (id);
+ if (addin != null) {
+ var paths = new List<string> ();
+ // Using addin.Module.ParentAddinDescription here because addin.Addin.Description may not
+ // have a valid reference (the description is lazy loaded and may already have been removed from the registry)
+ foreach (ModuleDescription mod in addin.Module.ParentAddinDescription.AllModules) {
+ foreach (Extension ext in mod.Extensions) {
+ if (!paths.Contains (ext.Path))
+ paths.Add (ext.Path);
}
}
-
- // Notify global extension point changes.
- // The event is called for all extensions, even for those not loaded. This is for coherence,
- // although that something that it doesn't make much sense to do (subscribing the ExtensionChanged
- // event without first getting the list of nodes that may change).
-
- // We get the runtime add-in because the add-in may already have been deleted from the registry
- RuntimeAddin addin = AddinEngine.GetAddin (id);
- if (addin != null) {
- var paths = new List<string> ();
- // Using addin.Module.ParentAddinDescription here because addin.Addin.Description may not
- // have a valid reference (the description is lazy loaded and may already have been removed from the registry)
- foreach (ModuleDescription mod in addin.Module.ParentAddinDescription.AllModules) {
- foreach (Extension ext in mod.Extensions) {
- if (!paths.Contains (ext.Path))
- paths.Add (ext.Path);
- }
- }
- foreach (string path in paths)
- NotifyExtensionsChanged (new ExtensionEventArgs (path));
- }
- } finally {
- fireEvents = false;
+ foreach (string path in paths)
+ NotifyExtensionsChanged (new ExtensionEventArgs (path));
}
}
@@ -1032,58 +1001,43 @@ namespace Mono.Addins
// contains extension nodes implemented in an add-in which is
// not loaded, the add-in will be automatically loaded
- internal void LoadExtensions (string requestedExtensionPath)
+ internal void LoadExtensions (ExtensionContextTransaction transaction, ExtensionPoint ep, TreeNodeBuilder node)
{
- TreeNode node = GetNode (requestedExtensionPath);
- if (node == null)
- throw new InvalidOperationException ("Extension point not defined: " + requestedExtensionPath);
-
- ExtensionPoint ep = node.ExtensionPoint;
-
- if (ep != null) {
-
- // Collect extensions to be loaded from add-ins. Before loading the extensions,
- // they must be sorted, that's why loading is split in two steps (collecting + loading).
-
- var addins = GetAddinsForPath (ep.Addins);
- var loadData = new List<ExtensionLoadData> (addins.Count);
-
- foreach (string addin in addins) {
- ExtensionLoadData ed = GetAddinExtensions (addin, ep);
- if (ed != null) {
- // Insert the addin data taking into account dependencies.
- // An add-in must be processed after all its dependencies.
- bool added = false;
- for (int n=0; n<loadData.Count; n++) {
- ExtensionLoadData other = loadData [n];
- if (AddinEngine.Registry.AddinDependsOn (other.AddinName, ed.AddinName)) {
- loadData.Insert (n, ed);
- added = true;
- break;
- }
+ // Collect extensions to be loaded from add-ins. Before loading the extensions,
+ // they must be sorted, that's why loading is split in two steps (collecting + loading).
+
+ var addins = GetAddinsForPath (ep.Addins);
+ var loadData = new List<ExtensionLoadData> (addins.Count);
+
+ foreach (string addin in addins) {
+ ExtensionLoadData ed = GetAddinExtensions (addin, ep);
+ if (ed != null) {
+ // Insert the addin data taking into account dependencies.
+ // An add-in must be processed after all its dependencies.
+ bool added = false;
+ for (int n=0; n<loadData.Count; n++) {
+ ExtensionLoadData other = loadData [n];
+ if (AddinEngine.Registry.AddinDependsOn (other.AddinName, ed.AddinName)) {
+ loadData.Insert (n, ed);
+ added = true;
+ break;
}
- if (!added)
- loadData.Add (ed);
}
+ if (!added)
+ loadData.Add (ed);
}
+ }
- // Now load the extensions
+ // Now load the extensions
- var loadedNodes = new List<TreeNode> ();
- foreach (ExtensionLoadData data in loadData) {
- foreach (Extension ext in data.Extensions) {
- TreeNode cnode = GetNode (ext.Path);
- if (cnode != null && cnode.ExtensionNodeSet != null)
- LoadModuleExtensionNodes (ext, data.AddinId, cnode.ExtensionNodeSet, loadedNodes);
- else
- AddinEngine.ReportError ("Extension node not found or not extensible: " + ext.Path, data.AddinId, null, false);
- }
+ foreach (ExtensionLoadData data in loadData) {
+ foreach (Extension ext in data.Extensions) {
+ var cnode = node.GetNode (ext.Path);
+ if (cnode != null)
+ LoadModuleExtensionNodes (transaction, cnode, ext, data.AddinId);
+ else
+ AddinEngine.ReportError ("Extension node not found or not extensible: " + ext.Path, data.AddinId, null, false);
}
- // Call the OnAddinLoaded method on nodes, if the add-in is already loaded
- foreach (TreeNode nod in loadedNodes)
- nod.ExtensionNode.OnAddinLoaded ();
-
- NotifyExtensionsChanged (new ExtensionEventArgs (requestedExtensionPath));
}
}
@@ -1138,18 +1092,18 @@ namespace Mono.Addins
}
}
- void LoadModuleExtensionNodes (Extension extension, string addinId, ExtensionNodeSet nset, List<TreeNode> loadedNodes)
+ void LoadModuleExtensionNodes (ExtensionContextTransaction transaction, TreeNodeBuilder node, Extension extension, string addinId)
{
// Now load the extensions
var addedNodes = new List<TreeNode> ();
- tree.LoadExtension (addinId, extension, addedNodes);
+ tree.LoadExtension (transaction, node, addinId, extension, addedNodes);
RuntimeAddin ad = AddinEngine.GetAddin (addinId);
if (ad != null) {
foreach (TreeNode nod in addedNodes) {
// Don't call OnAddinLoaded here. Do it when the entire extension point has been loaded.
- if (nod.ExtensionNode != null)
- loadedNodes.Add (nod);
+ if (nod.HasExtensionNode)
+ transaction.ReportLoadedNode (nod);
}
}
}
@@ -1173,11 +1127,13 @@ namespace Mono.Addins
TreeNode node = tree.GetNode (path);
if (node != null || parentContext == null)
return node;
-
+
TreeNode supNode = parentContext.tree.GetNode (path);
if (supNode == null)
return null;
-
+
+ // Node not found and the context has a parent context which has the node
+
if (path.StartsWith ("/"))
path = path.Substring (1);
@@ -1186,34 +1142,42 @@ namespace Mono.Addins
TreeNode dstNode = tree;
foreach (string part in parts) {
-
- // Look for the node in the source tree
-
- int i = srcNode.Children.IndexOfNode (part);
- if (i != -1)
- srcNode = srcNode.Children [i];
- else
+
+ // Look for the node in the source tree (from parent context)
+
+ srcNode = srcNode.GetChildNode (part);
+ if (srcNode == null)
return null;
// Now get the node in the target tree
-
- int j = dstNode.Children.IndexOfNode (part);
- if (j != -1) {
- dstNode = dstNode.Children [j];
+
+ var dstNodeChild = dstNode.GetChildNode (part);
+ if (dstNodeChild != null) {
+ dstNode = dstNodeChild;
}
else {
- // Create if not found
- TreeNode newNode = new TreeNode (AddinEngine, part);
- dstNode.AddChildNode (newNode);
- dstNode = newNode;
-
- // Copy extension data
- dstNode.ExtensionNodeSet = srcNode.ExtensionNodeSet;
- dstNode.ExtensionPoint = srcNode.ExtensionPoint;
- dstNode.Condition = srcNode.Condition;
-
- if (dstNode.Condition != null)
- RegisterNodeCondition (dstNode, dstNode.Condition);
+ using var transaction = BeginTransaction ();
+
+ // Check again just in case the node was created while taking the transaction
+ dstNodeChild = dstNode.GetChildNode (part);
+ if (dstNodeChild != null)
+ dstNode = dstNodeChild;
+ else {
+
+ // Create if not found
+ // Copy extension data from the parent context node
+
+ TreeNode newNode = srcNode.Clone (AddinEngine);
+
+ if (newNode.Condition != null)
+ transaction.RegisterNodeCondition (newNode, newNode.Condition);
+
+ // Don't rise extension change events since we are just building the tree, not modifying it
+ transaction.DisableEvents = true;
+
+ dstNode.AddChildNode (transaction, newNode);
+ dstNode = newNode;
+ }
}
}
diff --git a/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs b/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs
new file mode 100644
index 0000000..0a5f15c
--- /dev/null
+++ b/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs
@@ -0,0 +1,117 @@
+//
+// ExtensionContext.cs
+//
+// Author:
+// Lluis Sanchez Gual
+//
+// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Collections.Generic;
+
+namespace Mono.Addins
+{
+ class ExtensionContextTransaction : IDisposable
+ {
+ List<TreeNode> loadedNodes;
+ HashSet<TreeNode> childrenChanged;
+ HashSet<string> extensionsChanged;
+ List<(TreeNode Node, BaseCondition Condition)> nodeConditions;
+ List<(TreeNode Node, BaseCondition Condition)> nodeConditionUnregistrations;
+
+ public ExtensionContextTransaction (ExtensionContext context)
+ {
+ Context = context;
+ }
+
+ public ExtensionContext Context { get; }
+
+ public bool DisableEvents { get; set; }
+
+ public void Dispose ()
+ {
+ if (nodeConditions != null) {
+ Context.BulkRegisterNodeConditions (this, nodeConditions);
+ }
+
+ if (nodeConditionUnregistrations != null) {
+ Context.BulkUnregisterNodeConditions (this, nodeConditionUnregistrations);
+ }
+
+ if (loadedNodes != null) {
+ foreach (var node in loadedNodes)
+ node.NotifyAddinLoaded ();
+ }
+ if (childrenChanged != null) {
+ foreach (var node in childrenChanged) {
+ if (node.NotifyChildrenChanged ())
+ NotifyExtensionsChangedEvent (node.GetPath ());
+ }
+ }
+ if (extensionsChanged != null) {
+ foreach (var path in extensionsChanged)
+ Context.NotifyExtensionsChanged(new ExtensionEventArgs(path));
+ }
+ }
+
+ public void ReportLoadedNode (TreeNode node)
+ {
+ if (loadedNodes == null)
+ loadedNodes = new List<TreeNode> ();
+ loadedNodes.Add (node);
+ }
+
+ public void ReportChildrenChanged (TreeNode node)
+ {
+ if (!DisableEvents) {
+ if (childrenChanged == null)
+ childrenChanged = new HashSet<TreeNode> ();
+ childrenChanged.Add (node);
+ }
+ }
+
+ public void NotifyExtensionsChangedEvent (string path)
+ {
+ if (!DisableEvents) {
+ if (extensionsChanged == null)
+ extensionsChanged = new HashSet<string> ();
+ extensionsChanged.Add (path);
+ }
+ }
+
+ public void RegisterNodeCondition (TreeNode node, BaseCondition cond)
+ {
+ if (nodeConditions == null)
+ nodeConditions = new List<(TreeNode Node, BaseCondition Condition)> ();
+ nodeConditions.Add ((node, cond));
+ }
+
+ public void UnregisterNodeCondition (TreeNode node, BaseCondition cond)
+ {
+ if (nodeConditionUnregistrations == null)
+ nodeConditionUnregistrations = new List<(TreeNode Node, BaseCondition Condition)> ();
+ nodeConditionUnregistrations.Add ((node, cond));
+ }
+ }
+}
diff --git a/Mono.Addins/Mono.Addins/ExtensionNode.cs b/Mono.Addins/Mono.Addins/ExtensionNode.cs
index 09dd0c9..5452dcd 100644
--- a/Mono.Addins/Mono.Addins/ExtensionNode.cs
+++ b/Mono.Addins/Mono.Addins/ExtensionNode.cs
@@ -33,6 +33,8 @@ using System.Collections.Generic;
using System.Xml;
using System.Reflection;
using Mono.Addins.Description;
+using System.Threading;
+using System.Linq;
namespace Mono.Addins
{
@@ -59,7 +61,8 @@ namespace Mono.Addins
ModuleDescription module;
AddinEngine addinEngine;
event ExtensionNodeEventHandler extensionNodeChanged;
-
+ object localLock = new object ();
+
/// <summary>
/// Identifier of the node.
/// </summary>
@@ -192,41 +195,14 @@ namespace Mono.Addins
/// </summary>
public ExtensionNodeList ChildNodes {
get {
- if (childrenLoaded)
- return childNodes;
-
- try {
- if (treeNode.Children.Count == 0) {
- childNodes = ExtensionNodeList.Empty;
- return childNodes;
- }
- }
- catch (Exception ex) {
- addinEngine.ReportError (null, null, ex, false);
- childNodes = ExtensionNodeList.Empty;
- return childNodes;
- } finally {
- childrenLoaded = true;
- }
-
- List<ExtensionNode> list = new List<ExtensionNode> ();
- foreach (TreeNode cn in treeNode.Children) {
-
- // For each node check if it is visible for the current context.
- // If something fails while evaluating the condition, just ignore the node.
-
- try {
- if (cn.ExtensionNode != null && cn.IsEnabled)
- list.Add (cn.ExtensionNode);
- } catch (Exception ex) {
- addinEngine.ReportError (null, null, ex, false);
+ if (!childrenLoaded) {
+ lock (localLock) {
+ if (!childrenLoaded) {
+ childNodes = CreateChildrenList ();
+ childrenLoaded = true;
+ }
}
- }
- if (list.Count > 0)
- childNodes = new ExtensionNodeList (list);
- else
- childNodes = ExtensionNodeList.Empty;
-
+ }
return childNodes;
}
}
@@ -349,10 +325,12 @@ namespace Mono.Addins
Array GetChildObjectsInternal (Type arrayElementType, bool reuseCachedInstance)
{
- ArrayList list = new ArrayList (ChildNodes.Count);
+ var children = ChildNodes;
+
+ ArrayList list = new ArrayList (children.Count);
- for (int n=0; n<ChildNodes.Count; n++) {
- InstanceExtensionNode node = ChildNodes [n] as InstanceExtensionNode;
+ for (int n=0; n<children.Count; n++) {
+ var node = children [n] as InstanceExtensionNode;
if (node == null) {
addinEngine.ReportError ("Error while getting object for node in path '" + Path + "'. Extension node is not a subclass of InstanceExtensionNode.", null, null, false);
continue;
@@ -471,32 +449,85 @@ namespace Mono.Addins
return addinEngine.DefaultLocalizer;
}
-
+
+ ExtensionNodeList CreateChildrenList ()
+ {
+ var nodeChildren = treeNode.Children;
+
+ try {
+ if (nodeChildren.Length == 0) {
+ return ExtensionNodeList.Empty;
+ }
+ } catch (Exception ex) {
+ addinEngine.ReportError (null, null, ex, false);
+ return ExtensionNodeList.Empty;
+ }
+
+ List<ExtensionNode> list = new List<ExtensionNode> (nodeChildren.Length);
+ foreach (TreeNode cn in nodeChildren) {
+
+ // For each node check if it is visible for the current context.
+ // If something fails while evaluating the condition, just ignore the node.
+
+ try {
+ if (cn.IsEnabled && cn.ExtensionNode != null)
+ list.Add (cn.ExtensionNode);
+ } catch (Exception ex) {
+ addinEngine.ReportError (null, null, ex, false);
+ }
+ }
+
+ if (list.Count > 0)
+ return new ExtensionNodeList (list);
+ else
+ return ExtensionNodeList.Empty;
+ }
+
internal bool NotifyChildChanged ()
{
+ // If children are not loaded, there isn't anything to update
if (!childrenLoaded)
return false;
- ExtensionNodeList oldList = childNodes;
- childrenLoaded = false;
-
- bool changed = false;
-
- foreach (ExtensionNode nod in oldList) {
- if (ChildNodes [nod.Id] == null) {
- changed = true;
- OnChildNodeRemoved (nod);
+ List<(ExtensionNode Node, bool Added)> changes = null;
+
+ lock (localLock) {
+ var oldList = childNodes;
+ var newList = CreateChildrenList ();
+ childNodes = newList;
+
+ // Compare old list and new list to find out which nodes have
+ // been added or removed, since the changes need to be notified.
+
+ foreach (ExtensionNode nod in oldList) {
+ if (newList [nod.Id] == null) {
+ if (changes == null)
+ changes = new ();
+ changes.Add ((nod, false));
+ }
}
- }
- foreach (ExtensionNode nod in ChildNodes) {
- if (oldList [nod.Id] == null) {
- changed = true;
- OnChildNodeAdded (nod);
+ foreach (ExtensionNode nod in newList) {
+ if (oldList [nod.Id] == null) {
+ if (changes == null)
+ changes = new ();
+ changes.Add ((nod, true));
+ }
}
}
- if (changed)
+
+ // Notify the changes outside the lock
+
+ if (changes != null) {
+ foreach (var change in changes) {
+ if (change.Added)
+ OnChildNodeAdded (change.Node);
+ else
+ OnChildNodeRemoved (change.Node);
+ }
OnChildrenChanged ();
- return changed;
+ return true;
+ } else
+ return false;
}
/// <summary>
@@ -530,8 +561,7 @@ namespace Mono.Addins
/// </param>
protected virtual void OnChildNodeAdded (ExtensionNode node)
{
- if (extensionNodeChanged != null)
- extensionNodeChanged (this, new ExtensionNodeEventArgs (ExtensionChange.Add, node));
+ extensionNodeChanged?.Invoke (this, new ExtensionNodeEventArgs (ExtensionChange.Add, node));
}
/// <summary>
@@ -542,8 +572,7 @@ namespace Mono.Addins
/// </param>
protected virtual void OnChildNodeRemoved (ExtensionNode node)
{
- if (extensionNodeChanged != null)
- extensionNodeChanged (this, new ExtensionNodeEventArgs (ExtensionChange.Remove, node));
+ extensionNodeChanged?.Invoke (this, new ExtensionNodeEventArgs (ExtensionChange.Remove, node));
}
}
diff --git a/Mono.Addins/Mono.Addins/ExtensionNodeList.cs b/Mono.Addins/Mono.Addins/ExtensionNodeList.cs
index 729b5ae..b2a6586 100644
--- a/Mono.Addins/Mono.Addins/ExtensionNodeList.cs
+++ b/Mono.Addins/Mono.Addins/ExtensionNodeList.cs
@@ -36,7 +36,7 @@ namespace Mono.Addins
/// <summary>
/// A list of extension nodes.
/// </summary>
- public class ExtensionNodeList: IEnumerable
+ public class ExtensionNodeList: IEnumerable, IEnumerable<ExtensionNode>
{
internal List<ExtensionNode> list;
@@ -55,10 +55,7 @@ namespace Mono.Addins
/// </param>
public ExtensionNode this [int n] {
get {
- if (list == null)
- throw new System.IndexOutOfRangeException ();
- else
- return (ExtensionNode) list [n];
+ return (ExtensionNode) list [n];
}
}
@@ -70,14 +67,10 @@ namespace Mono.Addins
/// </param>
public ExtensionNode this [string id] {
get {
- if (list == null)
- return null;
- else {
- for (int n = list.Count - 1; n >= 0; n--)
- if (((ExtensionNode) list [n]).Id == id)
- return (ExtensionNode) list [n];
- return null;
- }
+ for (int n = list.Count - 1; n >= 0; n--)
+ if (list [n].Id == id)
+ return list [n];
+ return null;
}
}
@@ -86,8 +79,6 @@ namespace Mono.Addins
/// </summary>
public IEnumerator GetEnumerator ()
{
- if (list == null)
- return ((IList)Type.EmptyTypes).GetEnumerator ();
return list.GetEnumerator ();
}
@@ -95,7 +86,7 @@ namespace Mono.Addins
/// Number of nodes of the collection.
/// </summary>
public int Count {
- get { return list == null ? 0 : list.Count; }
+ get { return list.Count; }
}
/// <summary>
@@ -109,8 +100,12 @@ namespace Mono.Addins
/// </param>
public void CopyTo (ExtensionNode[] array, int index)
{
- if (list != null)
- list.CopyTo (array, index);
+ list.CopyTo (array, index);
+ }
+
+ IEnumerator<ExtensionNode> IEnumerable<ExtensionNode>.GetEnumerator ()
+ {
+ return list.GetEnumerator ();
}
}
diff --git a/Mono.Addins/Mono.Addins/ExtensionTree.cs b/Mono.Addins/Mono.Addins/ExtensionTree.cs
index ab49492..3b571a4 100644
--- a/Mono.Addins/Mono.Addins/ExtensionTree.cs
+++ b/Mono.Addins/Mono.Addins/ExtensionTree.cs
@@ -53,19 +53,18 @@ namespace Mono.Addins
}
- public void LoadExtension (string addin, Extension extension, List<TreeNode> addedNodes)
+ public void LoadExtension (ExtensionContextTransaction transaction, TreeNodeBuilder tnode, string addin, Extension extension, List<TreeNode> addedNodes)
{
- TreeNode tnode = GetNode (extension.Path);
if (tnode == null) {
addinEngine.ReportError ("Can't load extensions for path '" + extension.Path + "'. Extension point not defined.", addin, null, false);
return;
}
int curPos = -1;
- LoadExtensionElement (tnode, addin, extension.ExtensionNodes, (ModuleDescription) extension.Parent, ref curPos, tnode.Condition, false, addedNodes);
+ LoadExtensionElement (transaction, tnode, addin, extension.ExtensionNodes, (ModuleDescription) extension.Parent, ref curPos, tnode.Condition, false, addedNodes);
}
- void LoadExtensionElement (TreeNode tnode, string addin, ExtensionNodeDescriptionCollection extension, ModuleDescription module, ref int curPos, BaseCondition parentCondition, bool inComplextCondition, List<TreeNode> addedNodes)
+ void LoadExtensionElement (ExtensionContextTransaction transaction, TreeNodeBuilder tnode, string addin, ExtensionNodeDescriptionCollection extension, ModuleDescription module, ref int curPos, BaseCondition parentCondition, bool inComplextCondition, List<TreeNode> addedNodes)
{
foreach (ExtensionNodeDescription elem in extension) {
@@ -76,26 +75,23 @@ namespace Mono.Addins
}
if (elem.NodeName == "ComplexCondition") {
- LoadExtensionElement (tnode, addin, elem.ChildNodes, module, ref curPos, parentCondition, true, addedNodes);
+ LoadExtensionElement (transaction, tnode, addin, elem.ChildNodes, module, ref curPos, parentCondition, true, addedNodes);
continue;
}
if (elem.NodeName == "Condition") {
Condition cond = new Condition (AddinEngine, elem, parentCondition);
- LoadExtensionElement (tnode, addin, elem.ChildNodes, module, ref curPos, cond, false, addedNodes);
+ LoadExtensionElement (transaction, tnode, addin, elem.ChildNodes, module, ref curPos, cond, false, addedNodes);
continue;
}
- var pnode = tnode;
- ExtensionPoint extensionPoint = null;
- while (pnode != null && (extensionPoint = pnode.ExtensionPoint) == null)
- pnode = pnode.Parent;
+ ExtensionPoint extensionPoint = tnode.GetExtensionPoint ();
string after = elem.GetAttribute ("insertafter");
if (after.Length == 0 && extensionPoint != null && curPos == -1)
after = extensionPoint.DefaultInsertAfter;
if (after.Length > 0) {
- int i = tnode.Children.IndexOfNode (after);
+ int i = tnode.IndexOfChild (after);
if (i != -1)
curPos = i+1;
}
@@ -103,7 +99,7 @@ namespace Mono.Addins
if (before.Length == 0 && extensionPoint != null && curPos == -1)
before = extensionPoint.DefaultInsertBefore;
if (before.Length > 0) {
- int i = tnode.Children.IndexOfNode (before);
+ int i = tnode.IndexOfChild (before);
if (i != -1)
curPos = i;
}
@@ -124,7 +120,7 @@ namespace Mono.Addins
if (id.Length == 0)
id = AutoIdPrefix + (++internalId);
- TreeNode cnode = new TreeNode (addinEngine, id);
+ TreeNodeBuilder cnode = new TreeNodeBuilder (addinEngine, id);
ExtensionNode enode = ReadNode (cnode, addin, ntype, elem, module);
if (enode == null)
@@ -132,22 +128,16 @@ namespace Mono.Addins
cnode.Condition = parentCondition;
cnode.ExtensionNodeSet = ntype;
- tnode.InsertChildNode (curPos, cnode);
- addedNodes.Add (cnode);
+ tnode.InsertChild (curPos, cnode);
- if (cnode.Condition != null)
- Context.RegisterNodeCondition (cnode, cnode.Condition);
-
// Load children
if (elem.ChildNodes.Count > 0) {
int cp = 0;
- LoadExtensionElement (cnode, addin, elem.ChildNodes, module, ref cp, parentCondition, false, addedNodes);
+ LoadExtensionElement (transaction, cnode, addin, elem.ChildNodes, module, ref cp, parentCondition, false, addedNodes);
}
curPos++;
}
- if (Context.FireEvents)
- tnode.NotifyChildrenChanged ();
}
BaseCondition ReadComplexCondition (ExtensionNodeDescription elem, BaseCondition parentCondition)
@@ -176,7 +166,7 @@ namespace Mono.Addins
return new NullCondition ();
}
- public ExtensionNode ReadNode (TreeNode tnode, string addin, ExtensionNodeType ntype, ExtensionNodeDescription elem, ModuleDescription module)
+ public ExtensionNode ReadNode (TreeNodeBuilder tnode, string addin, ExtensionNodeType ntype, ExtensionNodeDescription elem, ModuleDescription module)
{
try {
if (ntype.Type == null) {
diff --git a/Mono.Addins/Mono.Addins/TreeNode.cs b/Mono.Addins/Mono.Addins/TreeNode.cs
index e7fa478..827545c 100644
--- a/Mono.Addins/Mono.Addins/TreeNode.cs
+++ b/Mono.Addins/Mono.Addins/TreeNode.cs
@@ -32,13 +32,14 @@ using System.Text;
using System.Collections;
using Mono.Addins.Description;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
namespace Mono.Addins
{
- class TreeNode
+ class TreeNode
{
- List<TreeNode> childrenList;
- TreeNodeCollection children;
+ ImmutableArray<TreeNode> children;
ExtensionNode extensionNode;
bool childrenLoaded;
string id;
@@ -52,7 +53,9 @@ namespace Mono.Addins
{
this.id = id;
this.addinEngine = addinEngine;
-
+
+ children = ImmutableArray<TreeNode>.Empty;
+
// Root node
if (id.Length == 0)
childrenLoaded = true;
@@ -83,7 +86,29 @@ namespace Mono.Addins
return extensionNode;
}
}
-
+
+ public void NotifyAddinUnloaded ()
+ {
+ extensionNode?.OnAddinUnloaded ();
+ }
+
+ public void NotifyAddinLoaded ()
+ {
+ extensionNode?.OnAddinLoaded ();
+ }
+
+ public bool HasExtensionNode {
+ get {
+ return extensionPoint != null;
+ }
+ }
+
+ public string AddinId {
+ get {
+ return extensionPoint?.RootAddin;
+ }
+ }
+
public ExtensionPoint ExtensionPoint {
get { return extensionPoint; }
set { extensionPoint = value; }
@@ -130,29 +155,27 @@ namespace Mono.Addins
get { return childrenLoaded; }
}
- public void AddChildNode (TreeNode node)
+ public void AddChildNode (ExtensionContextTransaction transaction, TreeNode node)
{
node.parent = this;
- if (childrenList == null)
- childrenList = new List<TreeNode> ();
- childrenList.Add (node);
+ children = children.Add (node);
+ transaction.ReportChildrenChanged (this);
}
- public void InsertChildNode (int n, TreeNode node)
+ internal void SetChildren (ExtensionContextTransaction transaction, ImmutableArray<TreeNode> children)
{
- node.parent = this;
- if (childrenList == null)
- childrenList = new List<TreeNode> ();
- childrenList.Insert (n, node);
-
- // Dont call NotifyChildrenChanged here. It is called by ExtensionTree,
- // after inserting all children of the node.
- }
-
- internal int ChildCount {
- get { return childrenList == null ? 0 : childrenList.Count; }
+ this.children = children;
+
+ if (childrenLoaded) {
+ // If children were already loaded, we need to notify that they have changed
+ transaction.ReportChildrenChanged (this);
+ } else {
+ // If children were not loaded, there is no need to notify change events,
+ // just set the loaded flag
+ childrenLoaded = true;
+ }
}
-
+
public ExtensionNode GetExtensionNode (string path, string childId)
{
TreeNode node = GetNode (path, childId);
@@ -186,39 +209,105 @@ namespace Mono.Addins
string[] parts = path.Split ('/');
TreeNode curNode = this;
- foreach (string part in parts) {
- int i = curNode.Children.IndexOfNode (part);
- if (i != -1) {
- curNode = curNode.Children [i];
+ for(int n=0; n<parts.Length; n++) {
+ var part = parts [n];
+ var node = curNode.GetChildNode (part);
+ if (node != null) {
+ curNode = node;
continue;
}
if (buildPath) {
- TreeNode newNode = new TreeNode (addinEngine, part);
- curNode.AddChildNode (newNode);
+ // A new branch has to be created. Begin a transaction
+ using var transaction = Context.BeginTransaction ();
+
+ // Don't rise events since we are just building the tree, not modifying it
+ transaction.DisableEvents = true;
+
+ // Build the branch
+ var nodeBuilder = new TreeNodeBuilder(addinEngine, part);
+ BuildNodePath (nodeBuilder, parts, n);
+
+ // Add the new node
+ var newNode = nodeBuilder.Build (transaction);
+ curNode.AddChildNode (transaction, newNode);
+
+ // Keep iterating to find the leaf
curNode = newNode;
} else
return null;
}
return curNode;
}
-
- public TreeNodeCollection Children {
+
+ void BuildNodePath (TreeNodeBuilder nodeBuilder, string[] parts, int partIndex)
+ {
+ for (int n=partIndex; n<parts.Length; n++) {
+ var id = parts [n];
+ var newNode = new TreeNodeBuilder (addinEngine, id);
+ nodeBuilder.AddChild (newNode);
+ nodeBuilder = newNode;
+ }
+ }
+
+ public TreeNode GetChildNode (string id)
+ {
+ var childrenList = Children;
+ foreach (var node in Children) {
+ if (node.Id == id)
+ return node;
+ }
+ return null;
+ }
+
+ public ImmutableArray<TreeNode> Children {
get {
if (!childrenLoaded) {
- childrenLoaded = true;
- if (extensionPoint != null)
- Context.LoadExtensions (GetPath ());
- // We have to keep the relation info, since add-ins may be loaded/unloaded
+ using var transaction = Context.BeginTransaction ();
+
+ // Check again after taking the lock
+ if (!childrenLoaded) {
+ if (extensionPoint != null) {
+ var builder = new TreeNodeBuilder (this);
+ Context.LoadExtensions (transaction, extensionPoint, builder);
+ builder.Build (transaction);
+ }
+ // We have to keep the relation info, since add-ins may be loaded/unloaded
+
+ childrenLoaded = true;
+ }
}
- if (childrenList == null)
- return TreeNodeCollection.Empty;
- if (children == null)
- children = new TreeNodeCollection (childrenList);
return children;
}
}
-
+
+ public TreeNode Clone (AddinEngine engine)
+ {
+ return Clone (engine, extensionPoint != null);
+ }
+
+ TreeNode Clone (AddinEngine engine, bool cloneChildren)
+ {
+ TreeNode newNode = new TreeNode (engine, Id);
+
+ // Copy extension data from the parent context node
+ newNode.ExtensionNodeSet = ExtensionNodeSet;
+ newNode.ExtensionPoint = ExtensionPoint;
+ newNode.Condition = Condition;
+
+ // If the node is an extension point, copy all the children to avoid
+ // having to load them from extensions again
+ if (cloneChildren && ChildrenLoaded) {
+ var builder = ImmutableArray.CreateBuilder<TreeNode> ();
+ foreach (var node in children) {
+ builder.Add (node.Clone (engine, true));
+ }
+ childrenLoaded = true;
+ newNode.children = builder.ToImmutableArray ();
+ }
+ return newNode;
+ }
+
public string GetPath ()
{
int num=0;
@@ -243,7 +332,7 @@ namespace Mono.Addins
if (extensionNode != null && extensionNode.AddinId == ad.Addin.Id)
extensionNode.OnAddinLoaded ();
if (recursive && childrenLoaded) {
- foreach (TreeNode node in Children.Clone ())
+ foreach (TreeNode node in Children)
node.NotifyAddinLoaded (ad, true);
}
}
@@ -257,9 +346,9 @@ namespace Mono.Addins
TreeNode curNode = this;
foreach (string part in parts) {
- int i = curNode.Children.IndexOfNode (part);
- if (i != -1) {
- curNode = curNode.Children [i];
+ var node = curNode.GetChildNode (part);
+ if (node != null) {
+ curNode = node;
if (!curNode.ChildrenLoaded)
return null;
if (curNode.ExtensionPoint != null)
@@ -285,7 +374,7 @@ namespace Mono.Addins
node.FindAddinNodes (id, nodes);
}
- if (id == null || (ExtensionNode != null && ExtensionNode.AddinId == id))
+ if (id == null || AddinId == id)
nodes.Add (this);
}
@@ -319,13 +408,13 @@ namespace Mono.Addins
return false;
}
- public void Remove ()
+ public void Remove (ExtensionContextTransaction transaction)
{
if (parent != null) {
if (Condition != null)
- Context.UnregisterNodeCondition (this, Condition);
- parent.childrenList.Remove (this);
- parent.NotifyChildrenChanged ();
+ transaction.UnregisterNodeCondition (this, Condition);
+ parent.children = parent.children.Remove(this);
+ transaction.ReportChildrenChanged(parent);
}
}
@@ -337,7 +426,7 @@ namespace Mono.Addins
return false;
}
- public void ResetCachedData ()
+ public void ResetCachedData (ExtensionContextTransaction transaction)
{
if (extensionPoint != null) {
string aid = Addin.GetIdName (extensionPoint.ParentAddinDescription.AddinId);
@@ -345,10 +434,10 @@ namespace Mono.Addins
if (ad != null)
extensionPoint = ad.Addin.Description.ExtensionPoints [GetPath ()];
}
- if (childrenList != null) {
- foreach (TreeNode cn in childrenList)
- cn.ResetCachedData ();
+ if (childrenLoaded) {
+ foreach (TreeNode cn in children)
+ cn.ResetCachedData (transaction);
}
}
- }
+ }
}
diff --git a/Mono.Addins/Mono.Addins/TreeNodeBuilder.cs b/Mono.Addins/Mono.Addins/TreeNodeBuilder.cs
new file mode 100644
index 0000000..df72fa4
--- /dev/null
+++ b/Mono.Addins/Mono.Addins/TreeNodeBuilder.cs
@@ -0,0 +1,177 @@
+//
+// TreeNode.cs
+//
+// Author:
+// Lluis Sanchez Gual
+//
+// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using Mono.Addins.Description;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace Mono.Addins
+{
+ class TreeNodeBuilder
+ {
+ AddinEngine addinEngine;
+ string id;
+ List<TreeNodeBuilder> children;
+ ExtensionNode extensionNode;
+ string path;
+ TreeNode existingNode;
+
+ public TreeNodeBuilder (TreeNode existingNode)
+ {
+ this.existingNode = existingNode;
+ this.id = existingNode.Id;
+ }
+
+ public TreeNodeBuilder (AddinEngine addinEngine, string id)
+ {
+ this.addinEngine = addinEngine;
+ this.id = id;
+ }
+
+ public string Id => id;
+
+ public ExtensionPoint GetExtensionPoint ()
+ {
+ if (existingNode != null)
+ return existingNode.ExtensionPoint;
+ if (Parent != null)
+ return Parent.GetExtensionPoint ();
+ return null;
+ }
+
+ public TreeNodeBuilder Parent { get; set; }
+
+ public IReadOnlyList<TreeNodeBuilder> Children => children;
+
+ public BaseCondition Condition { get; set; }
+
+ public ExtensionNodeType ExtensionNodeSet { get; set; }
+ public ExtensionPoint ExtensionPoint { get; internal set; }
+
+ public void AddChild (TreeNodeBuilder childBuilder)
+ {
+ childBuilder.Parent = this;
+ if (children == null)
+ children = new List<TreeNodeBuilder>();
+ children.Add (childBuilder);
+ }
+
+ public void InsertChild (int curPos, TreeNodeBuilder childBuilder)
+ {
+ childBuilder.Parent = this;
+ if (children == null)
+ children = new List<TreeNodeBuilder>();
+ children.Insert (curPos, childBuilder);
+ }
+
+ public int IndexOfChild (string id)
+ {
+ for (int n = 0; n < children.Count; n++) {
+ if (children[n].Id == id)
+ return n;
+ }
+ return -1;
+ }
+
+ public TreeNode Build (ExtensionContextTransaction transaction)
+ {
+ var childNodes = ImmutableArray.CreateRange (children.Select (builder => builder.Build (transaction)));
+ if (existingNode != null) {
+ existingNode.SetChildren (transaction, childNodes);
+ return existingNode;
+ } else {
+ TreeNode cnode = new TreeNode (addinEngine, id);
+ if (Condition != null)
+ cnode.Condition = Condition;
+ if (ExtensionNodeSet != null)
+ cnode.ExtensionNodeSet = ExtensionNodeSet;
+ if (extensionNode != null)
+ cnode.AttachExtensionNode (extensionNode);
+ cnode.SetChildren (transaction, childNodes);
+
+ if (cnode.Condition != null)
+ transaction.RegisterNodeCondition (cnode, cnode.Condition);
+
+ transaction.ReportLoadedNode (cnode);
+ return cnode;
+ }
+ }
+
+ internal void AttachExtensionNode (ExtensionNode node)
+ {
+ extensionNode = node;
+ }
+
+ internal string GetPath ()
+ {
+ if (path == null) {
+ if (existingNode != null)
+ path = existingNode.GetPath ();
+ else {
+ if (Parent != null)
+ path = Parent.GetPath () + "/" + id;
+ else
+ throw new InvalidOperationException (); // Should not happen
+ }
+ }
+ return path;
+ }
+
+ public TreeNodeBuilder GetNode (string path)
+ {
+ var thisPath = GetPath ();
+
+ if (path == thisPath)
+ return this;
+
+ if (!path.StartsWith (thisPath))
+ throw new InvalidOperationException ("Invalid extension path. Should not happen!");
+
+ var childPath = path.Substring (thisPath.Length);
+ string[] parts = childPath.Split ('/');
+
+ var node = this;
+
+ foreach (var part in parts) {
+ if (string.IsNullOrEmpty (part))
+ continue;
+ int i = IndexOfChild (part);
+ if (i == -1) {
+ var child = new TreeNodeBuilder (addinEngine, part);
+ node.AddChild (child);
+ node = child;
+ } else
+ node = children [i];
+ }
+ return node;
+ }
+ }
+}
diff --git a/Mono.Addins/Mono.Addins/TreeNodeCollection.cs b/Mono.Addins/Mono.Addins/TreeNodeCollection.cs
deleted file mode 100644
index 9da13b7..0000000
--- a/Mono.Addins/Mono.Addins/TreeNodeCollection.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-//
-// TreeNodeCollection.cs
-//
-// Author:
-// Lluis Sanchez Gual
-//
-// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Mono.Addins
-{
- class TreeNodeCollection: IEnumerable
- {
- List<TreeNode> list;
-
- internal static TreeNodeCollection Empty = new TreeNodeCollection (null);
-
- public TreeNodeCollection (List<TreeNode> list)
- {
- this.list = list;
- }
-
- public IEnumerator GetEnumerator ()
- {
- if (list != null)
- return list.GetEnumerator ();
- else
- return Type.EmptyTypes.GetEnumerator ();
- }
-
- public TreeNode this [int n] {
- get {
- if (list != null)
- return list [n];
- else
- throw new System.IndexOutOfRangeException ();
- }
- }
-
- public int IndexOfNode (string id)
- {
- for (int n=0; n<Count; n++) {
- if (this [n].Id == id)
- return n;
- }
- return -1;
- }
-
- public int Count {
- get { return list != null ? list.Count : 0; }
- }
-
- public TreeNodeCollection Clone ()
- {
- if (list != null)
- return new TreeNodeCollection (list.ToList ());
- else
- return Empty;
- }
- }
-}