diff options
author | Lluis Sanchez <llsan@microsoft.com> | 2022-06-10 12:18:20 +0300 |
---|---|---|
committer | Lluis Sanchez <llsan@microsoft.com> | 2022-09-13 20:38:41 +0300 |
commit | 448354374f80513a84a2b547b87c66ef7050dda6 (patch) | |
tree | ff115a7ac94bfb40072bb595b3689bc08810881b | |
parent | 10d055187ecf7df8145b426b3f9122a73b358b60 (diff) |
Ongoing work to make mono.addins thread safe
-rw-r--r-- | Mono.Addins/Mono.Addins.Database/AddinDatabase.cs | 42 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins.csproj | 2 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins/AddinEngine.cs | 54 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins/ExtensionContext.cs | 608 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs | 117 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins/ExtensionNode.cs | 147 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins/ExtensionNodeList.cs | 31 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins/ExtensionTree.cs | 34 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins/TreeNode.cs | 195 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins/TreeNodeBuilder.cs | 177 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins/TreeNodeCollection.cs | 86 |
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; - } - } -} |