1 // 2 // ExtensionContext.cs 3 // 4 // Author: 5 // Lluis Sanchez Gual 6 // 7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com) 8 // 9 // Permission is hereby granted, free of charge, to any person obtaining 10 // a copy of this software and associated documentation files (the 11 // "Software"), to deal in the Software without restriction, including 12 // without limitation the rights to use, copy, modify, merge, publish, 13 // distribute, sublicense, and/or sell copies of the Software, and to 14 // permit persons to whom the Software is furnished to do so, subject to 15 // the following conditions: 16 // 17 // The above copyright notice and this permission notice shall be 18 // included in all copies or substantial portions of the Software. 19 // 20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 // 28 29 30 using System; 31 using System.Collections; 32 using System.Collections.Generic; 33 using System.Collections.Specialized; 34 using Mono.Addins.Description; 35 36 namespace Mono.Addins 37 { 38 /// <summary> 39 /// An extension context. 40 /// </summary> 41 /// <remarks> 42 /// Extension contexts can be used to query the extension tree 43 /// using particular condition values. Extension points which 44 /// declare the availability of a condition type can only be 45 /// queryed using an extension context which provides an 46 /// evaluator for that condition. 47 /// </remarks> 48 public class ExtensionContext 49 { 50 internal object LocalLock = new object (); 51 52 Hashtable conditionTypes = new Hashtable (); 53 Hashtable conditionsToNodes = new Hashtable (); 54 List<WeakReference> childContexts; 55 ExtensionContext parentContext; 56 ExtensionTree tree; 57 bool fireEvents = false; 58 59 ArrayList runTimeEnabledAddins; 60 ArrayList runTimeDisabledAddins; 61 62 /// <summary> 63 /// Extension change event. 64 /// </summary> 65 /// <remarks> 66 /// This event is fired when any extension point in the add-in system changes. 67 /// The event args object provides the path of the changed extension, although 68 /// it does not provide information about what changed. Hosts subscribing to 69 /// this event should get the new list of nodes using a query method such as 70 /// AddinManager.GetExtensionNodes() and then update whatever needs to be updated. 71 /// </remarks> 72 public event ExtensionEventHandler ExtensionChanged; 73 Initialize(AddinEngine addinEngine)74 internal void Initialize (AddinEngine addinEngine) 75 { 76 fireEvents = false; 77 tree = new ExtensionTree (addinEngine, this); 78 } 79 80 #pragma warning disable 1591 81 [ObsoleteAttribute] Clear()82 protected void Clear () 83 { 84 } 85 #pragma warning restore 1591 86 87 ClearContext()88 internal void ClearContext () 89 { 90 conditionTypes.Clear (); 91 conditionsToNodes.Clear (); 92 childContexts = null; 93 parentContext = null; 94 tree = null; 95 runTimeEnabledAddins = null; 96 runTimeDisabledAddins = null; 97 } 98 99 internal AddinEngine AddinEngine { 100 get { return tree.AddinEngine; } 101 } 102 CleanDisposedChildContexts()103 void CleanDisposedChildContexts () 104 { 105 if (childContexts != null) 106 childContexts.RemoveAll (w => w.Target == null); 107 } 108 ResetCachedData()109 internal virtual void ResetCachedData () 110 { 111 tree.ResetCachedData (); 112 if (childContexts != null) { 113 foreach (WeakReference wref in childContexts) { 114 ExtensionContext ctx = wref.Target as ExtensionContext; 115 if (ctx != null) 116 ctx.ResetCachedData (); 117 } 118 } 119 } 120 CreateChildContext()121 internal ExtensionContext CreateChildContext () 122 { 123 lock (conditionTypes) { 124 if (childContexts == null) 125 childContexts = new List<WeakReference> (); 126 else 127 CleanDisposedChildContexts (); 128 ExtensionContext ctx = new ExtensionContext (); 129 ctx.Initialize (AddinEngine); 130 ctx.parentContext = this; 131 WeakReference wref = new WeakReference (ctx); 132 childContexts.Add (wref); 133 return ctx; 134 } 135 } 136 137 /// <summary> 138 /// Registers a new condition in the extension context. 139 /// </summary> 140 /// <param name="id"> 141 /// Identifier of the condition. 142 /// </param> 143 /// <param name="type"> 144 /// Condition evaluator. 145 /// </param> 146 /// <remarks> 147 /// The registered condition will be particular to this extension context. 148 /// Any event that might be fired as a result of changes in the condition will 149 /// only be fired in this context. 150 /// </remarks> RegisterCondition(string id, ConditionType type)151 public void RegisterCondition (string id, ConditionType type) 152 { 153 type.Id = id; 154 ConditionInfo info = CreateConditionInfo (id); 155 ConditionType ot = info.CondType as ConditionType; 156 if (ot != null) 157 ot.Changed -= new EventHandler (OnConditionChanged); 158 info.CondType = type; 159 type.Changed += new EventHandler (OnConditionChanged); 160 } 161 162 /// <summary> 163 /// Registers a new condition in the extension context. 164 /// </summary> 165 /// <param name="id"> 166 /// Identifier of the condition. 167 /// </param> 168 /// <param name="type"> 169 /// Type of the condition evaluator. Must be a subclass of Mono.Addins.ConditionType. 170 /// </param> 171 /// <remarks> 172 /// The registered condition will be particular to this extension context. Any event 173 /// that might be fired as a result of changes in the condition will only be fired in this context. 174 /// </remarks> RegisterCondition(string id, Type type)175 public void RegisterCondition (string id, Type type) 176 { 177 // Allows delayed creation of condition types 178 ConditionInfo info = CreateConditionInfo (id); 179 ConditionType ot = info.CondType as ConditionType; 180 if (ot != null) 181 ot.Changed -= new EventHandler (OnConditionChanged); 182 info.CondType = type; 183 } 184 CreateConditionInfo(string id)185 ConditionInfo CreateConditionInfo (string id) 186 { 187 ConditionInfo info = conditionTypes [id] as ConditionInfo; 188 if (info == null) { 189 info = new ConditionInfo (); 190 conditionTypes [id] = info; 191 } 192 return info; 193 } 194 195 internal bool FireEvents { 196 get { return fireEvents; } 197 } 198 GetCondition(string id)199 internal ConditionType GetCondition (string id) 200 { 201 ConditionType ct; 202 ConditionInfo info = (ConditionInfo) conditionTypes [id]; 203 204 if (info != null) { 205 if (info.CondType is Type) { 206 // The condition was registered as a type, create an instance now 207 ct = (ConditionType) Activator.CreateInstance ((Type)info.CondType); 208 ct.Id = id; 209 ct.Changed += new EventHandler (OnConditionChanged); 210 info.CondType = ct; 211 } 212 else 213 ct = info.CondType as ConditionType; 214 215 if (ct != null) 216 return ct; 217 } 218 219 if (parentContext != null) 220 return parentContext.GetCondition (id); 221 else 222 return null; 223 } 224 RegisterNodeCondition(TreeNode node, BaseCondition cond)225 internal void RegisterNodeCondition (TreeNode node, BaseCondition cond) 226 { 227 ArrayList list = (ArrayList) conditionsToNodes [cond]; 228 if (list == null) { 229 list = new ArrayList (); 230 conditionsToNodes [cond] = list; 231 ArrayList conditionTypeIds = new ArrayList (); 232 cond.GetConditionTypes (conditionTypeIds); 233 234 foreach (string cid in conditionTypeIds) { 235 236 // Make sure the condition is properly created 237 GetCondition (cid); 238 239 ConditionInfo info = CreateConditionInfo (cid); 240 if (info.BoundConditions == null) 241 info.BoundConditions = new ArrayList (); 242 243 info.BoundConditions.Add (cond); 244 } 245 } 246 list.Add (node); 247 } 248 UnregisterNodeCondition(TreeNode node, BaseCondition cond)249 internal void UnregisterNodeCondition (TreeNode node, BaseCondition cond) 250 { 251 ArrayList list = (ArrayList) conditionsToNodes [cond]; 252 if (list == null) 253 return; 254 255 list.Remove (node); 256 if (list.Count == 0) { 257 conditionsToNodes.Remove (cond); 258 ArrayList conditionTypeIds = new ArrayList (); 259 cond.GetConditionTypes (conditionTypeIds); 260 foreach (string cid in conditionTypes.Keys) { 261 ConditionInfo info = conditionTypes [cid] as ConditionInfo; 262 if (info != null && info.BoundConditions != null) 263 info.BoundConditions.Remove (cond); 264 } 265 } 266 } 267 268 /// <summary> 269 /// Returns the extension node in a path 270 /// </summary> 271 /// <param name="path"> 272 /// Location of the node. 273 /// </param> 274 /// <returns> 275 /// The node, or null if not found. 276 /// </returns> GetExtensionNode(string path)277 public ExtensionNode GetExtensionNode (string path) 278 { 279 TreeNode node = GetNode (path); 280 if (node == null) 281 return null; 282 283 if (node.Condition == null || node.Condition.Evaluate (this)) 284 return node.ExtensionNode; 285 else 286 return null; 287 } 288 289 /// <summary> 290 /// Returns the extension node in a path 291 /// </summary> 292 /// <param name="path"> 293 /// Location of the node. 294 /// </param> 295 /// <returns> 296 /// The node, or null if not found. 297 /// </returns> 298 public T GetExtensionNode<T> (string path) where T: ExtensionNode 299 { 300 return (T) GetExtensionNode (path); 301 } 302 303 /// <summary> 304 /// Gets extension nodes registered in a path. 305 /// </summary> 306 /// <param name="path"> 307 /// An extension path.> 308 /// </param> 309 /// <returns> 310 /// All nodes registered in the provided path. 311 /// </returns> GetExtensionNodes(string path)312 public ExtensionNodeList GetExtensionNodes (string path) 313 { 314 return GetExtensionNodes (path, null); 315 } 316 317 /// <summary> 318 /// Gets extension nodes registered in a path. 319 /// </summary> 320 /// <param name="path"> 321 /// An extension path. 322 /// </param> 323 /// <returns> 324 /// A list of nodes 325 /// </returns> 326 /// <remarks> 327 /// This method returns all nodes registered under the provided path. 328 /// It will throw a InvalidOperationException if the type of one of 329 /// the registered nodes is not assignable to the provided type. 330 /// </remarks> 331 public ExtensionNodeList<T> GetExtensionNodes<T> (string path) where T: ExtensionNode 332 { 333 ExtensionNodeList nodes = GetExtensionNodes (path, typeof(T)); 334 return new ExtensionNodeList<T> (nodes.list); 335 } 336 337 /// <summary> 338 /// Gets extension nodes for a type extension point 339 /// </summary> 340 /// <param name="instanceType"> 341 /// Type defining the extension point 342 /// </param> 343 /// <returns> 344 /// A list of nodes 345 /// </returns> 346 /// <remarks> 347 /// This method returns all extension nodes bound to the provided type. 348 /// </remarks> GetExtensionNodes(Type instanceType)349 public ExtensionNodeList GetExtensionNodes (Type instanceType) 350 { 351 return GetExtensionNodes (instanceType, typeof(ExtensionNode)); 352 } 353 354 /// <summary> 355 /// Gets extension nodes for a type extension point 356 /// </summary> 357 /// <param name="instanceType"> 358 /// Type defining the extension point 359 /// </param> 360 /// <param name="expectedNodeType"> 361 /// Expected extension node type 362 /// </param> 363 /// <returns> 364 /// A list of nodes 365 /// </returns> 366 /// <remarks> 367 /// This method returns all nodes registered for the provided type. 368 /// It will throw a InvalidOperationException if the type of one of 369 /// the registered nodes is not assignable to the provided node type. 370 /// </remarks> GetExtensionNodes(Type instanceType, Type expectedNodeType)371 public ExtensionNodeList GetExtensionNodes (Type instanceType, Type expectedNodeType) 372 { 373 string path = AddinEngine.GetAutoTypeExtensionPoint (instanceType); 374 if (path == null) 375 return new ExtensionNodeList (null); 376 return GetExtensionNodes (path, expectedNodeType); 377 } 378 379 /// <summary> 380 /// Gets extension nodes for a type extension point 381 /// </summary> 382 /// <param name="instanceType"> 383 /// Type defining the extension point 384 /// </param> 385 /// <returns> 386 /// A list of nodes 387 /// </returns> 388 /// <remarks> 389 /// This method returns all nodes registered for the provided type. 390 /// It will throw a InvalidOperationException if the type of one of 391 /// the registered nodes is not assignable to the specified node type argument. 392 /// </remarks> 393 public ExtensionNodeList<T> GetExtensionNodes<T> (Type instanceType) where T: ExtensionNode 394 { 395 string path = AddinEngine.GetAutoTypeExtensionPoint (instanceType); 396 if (path == null) 397 return new ExtensionNodeList<T> (null); 398 return new ExtensionNodeList<T> (GetExtensionNodes (path, typeof (T)).list); 399 } 400 401 /// <summary> 402 /// Gets extension nodes registered in a path. 403 /// </summary> 404 /// <param name="path"> 405 /// An extension path. 406 /// </param> 407 /// <param name="expectedNodeType"> 408 /// Expected node type. 409 /// </param> 410 /// <returns> 411 /// A list of nodes 412 /// </returns> 413 /// <remarks> 414 /// This method returns all nodes registered under the provided path. 415 /// It will throw a InvalidOperationException if the type of one of 416 /// the registered nodes is not assignable to the provided type. 417 /// </remarks> GetExtensionNodes(string path, Type expectedNodeType)418 public ExtensionNodeList GetExtensionNodes (string path, Type expectedNodeType) 419 { 420 TreeNode node = GetNode (path); 421 if (node == null || node.ExtensionNode == null) 422 return ExtensionNodeList.Empty; 423 424 ExtensionNodeList list = node.ExtensionNode.ChildNodes; 425 426 if (expectedNodeType != null) { 427 bool foundError = false; 428 foreach (ExtensionNode cnode in list) { 429 if (!expectedNodeType.IsInstanceOfType (cnode)) { 430 foundError = true; 431 AddinEngine.ReportError ("Error while getting nodes for path '" + path + "'. Expected subclass of node type '" + expectedNodeType + "'. Found '" + cnode.GetType (), null, null, false); 432 } 433 } 434 if (foundError) { 435 // Create a new list excluding the elements that failed the test 436 List<ExtensionNode> newList = new List<ExtensionNode> (); 437 foreach (ExtensionNode cnode in list) { 438 if (expectedNodeType.IsInstanceOfType (cnode)) 439 newList.Add (cnode); 440 } 441 return new ExtensionNodeList (newList); 442 } 443 } 444 return list; 445 } 446 447 /// <summary> 448 /// Gets extension objects registered for a type extension point. 449 /// </summary> 450 /// <param name="instanceType"> 451 /// Type defining the extension point 452 /// </param> 453 /// <returns> 454 /// A list of objects 455 /// </returns> GetExtensionObjects(Type instanceType)456 public object[] GetExtensionObjects (Type instanceType) 457 { 458 return GetExtensionObjects (instanceType, true); 459 } 460 461 /// <summary> 462 /// Gets extension objects registered for a type extension point. 463 /// </summary> 464 /// <returns> 465 /// A list of objects 466 /// </returns> 467 /// <remarks> 468 /// The type argument of this generic method is the type that defines 469 /// the extension point. 470 /// </remarks> GetExtensionObjects()471 public T[] GetExtensionObjects<T> () 472 { 473 return GetExtensionObjects<T> (true); 474 } 475 476 /// <summary> 477 /// Gets extension objects registered for a type extension point. 478 /// </summary> 479 /// <param name="instanceType"> 480 /// Type defining the extension point 481 /// </param> 482 /// <param name="reuseCachedInstance"> 483 /// When set to True, it will return instances created in previous calls. 484 /// </param> 485 /// <returns> 486 /// A list of extension objects. 487 /// </returns> GetExtensionObjects(Type instanceType, bool reuseCachedInstance)488 public object[] GetExtensionObjects (Type instanceType, bool reuseCachedInstance) 489 { 490 string path = AddinEngine.GetAutoTypeExtensionPoint (instanceType); 491 if (path == null) 492 return (object[]) Array.CreateInstance (instanceType, 0); 493 return GetExtensionObjects (path, instanceType, reuseCachedInstance); 494 } 495 496 /// <summary> 497 /// Gets extension objects registered for a type extension point. 498 /// </summary> 499 /// <param name="reuseCachedInstance"> 500 /// When set to True, it will return instances created in previous calls. 501 /// </param> 502 /// <returns> 503 /// A list of extension objects. 504 /// </returns> 505 /// <remarks> 506 /// The type argument of this generic method is the type that defines 507 /// the extension point. 508 /// </remarks> GetExtensionObjects(bool reuseCachedInstance)509 public T[] GetExtensionObjects<T> (bool reuseCachedInstance) 510 { 511 string path = AddinEngine.GetAutoTypeExtensionPoint (typeof(T)); 512 if (path == null) 513 return new T[0]; 514 return GetExtensionObjects<T> (path, reuseCachedInstance); 515 } 516 517 /// <summary> 518 /// Gets extension objects registered in a path 519 /// </summary> 520 /// <param name="path"> 521 /// An extension path. 522 /// </param> 523 /// <returns> 524 /// An array of objects registered in the path. 525 /// </returns> 526 /// <remarks> 527 /// This method can only be used if all nodes in the provided extension path 528 /// are of type Mono.Addins.TypeExtensionNode. The returned array is composed 529 /// by all objects created by calling the TypeExtensionNode.CreateInstance() 530 /// method for each node. 531 /// </remarks> GetExtensionObjects(string path)532 public object[] GetExtensionObjects (string path) 533 { 534 return GetExtensionObjects (path, typeof(object), true); 535 } 536 537 /// <summary> 538 /// Gets extension objects registered in a path. 539 /// </summary> 540 /// <param name="path"> 541 /// An extension path. 542 /// </param> 543 /// <param name="reuseCachedInstance"> 544 /// When set to True, it will return instances created in previous calls. 545 /// </param> 546 /// <returns> 547 /// An array of objects registered in the path. 548 /// </returns> 549 /// <remarks> 550 /// This method can only be used if all nodes in the provided extension path 551 /// are of type Mono.Addins.TypeExtensionNode. The returned array is composed 552 /// by all objects created by calling the TypeExtensionNode.CreateInstance() 553 /// method for each node (or TypeExtensionNode.GetInstance() if 554 /// reuseCachedInstance is set to true) 555 /// </remarks> GetExtensionObjects(string path, bool reuseCachedInstance)556 public object[] GetExtensionObjects (string path, bool reuseCachedInstance) 557 { 558 return GetExtensionObjects (path, typeof(object), reuseCachedInstance); 559 } 560 561 /// <summary> 562 /// Gets extension objects registered in a path. 563 /// </summary> 564 /// <param name="path"> 565 /// An extension path. 566 /// </param> 567 /// <param name="arrayElementType"> 568 /// Type of the return array elements. 569 /// </param> 570 /// <returns> 571 /// An array of objects registered in the path. 572 /// </returns> 573 /// <remarks> 574 /// This method can only be used if all nodes in the provided extension path 575 /// are of type Mono.Addins.TypeExtensionNode. The returned array is composed 576 /// by all objects created by calling the TypeExtensionNode.CreateInstance() 577 /// method for each node. 578 /// 579 /// An InvalidOperationException exception is thrown if one of the found 580 /// objects is not a subclass of the provided type. 581 /// </remarks> GetExtensionObjects(string path, Type arrayElementType)582 public object[] GetExtensionObjects (string path, Type arrayElementType) 583 { 584 return GetExtensionObjects (path, arrayElementType, true); 585 } 586 587 /// <summary> 588 /// Gets extension objects registered in a path. 589 /// </summary> 590 /// <param name="path"> 591 /// An extension path. 592 /// </param> 593 /// <returns> 594 /// An array of objects registered in the path. 595 /// </returns> 596 /// <remarks> 597 /// This method can only be used if all nodes in the provided extension path 598 /// are of type Mono.Addins.TypeExtensionNode. The returned array is composed 599 /// by all objects created by calling the TypeExtensionNode.CreateInstance() 600 /// method for each node. 601 /// 602 /// An InvalidOperationException exception is thrown if one of the found 603 /// objects is not a subclass of the provided type. 604 /// </remarks> GetExtensionObjects(string path)605 public T[] GetExtensionObjects<T> (string path) 606 { 607 return GetExtensionObjects<T> (path, true); 608 } 609 610 /// <summary> 611 /// Gets extension objects registered in a path. 612 /// </summary> 613 /// <param name="path"> 614 /// An extension path. 615 /// </param> 616 /// <param name="reuseCachedInstance"> 617 /// When set to True, it will return instances created in previous calls. 618 /// </param> 619 /// <returns> 620 /// An array of objects registered in the path. 621 /// </returns> 622 /// <remarks> 623 /// This method can only be used if all nodes in the provided extension path 624 /// are of type Mono.Addins.TypeExtensionNode. The returned array is composed 625 /// by all objects created by calling the TypeExtensionNode.CreateInstance() 626 /// method for each node (or TypeExtensionNode.GetInstance() if 627 /// reuseCachedInstance is set to true). 628 /// 629 /// An InvalidOperationException exception is thrown if one of the found 630 /// objects is not a subclass of the provided type. 631 /// </remarks> GetExtensionObjects(string path, bool reuseCachedInstance)632 public T[] GetExtensionObjects<T> (string path, bool reuseCachedInstance) 633 { 634 ExtensionNode node = GetExtensionNode (path); 635 if (node == null) 636 throw new InvalidOperationException ("Extension node not found in path: " + path); 637 return node.GetChildObjects<T> (reuseCachedInstance); 638 } 639 640 /// <summary> 641 /// Gets extension objects registered in a path. 642 /// </summary> 643 /// <param name="path"> 644 /// An extension path. 645 /// </param> 646 /// <param name="arrayElementType"> 647 /// Type of the return array elements. 648 /// </param> 649 /// <param name="reuseCachedInstance"> 650 /// When set to True, it will return instances created in previous calls. 651 /// </param> 652 /// <returns> 653 /// An array of objects registered in the path. 654 /// </returns> 655 /// <remarks> 656 /// This method can only be used if all nodes in the provided extension path 657 /// are of type Mono.Addins.TypeExtensionNode. The returned array is composed 658 /// by all objects created by calling the TypeExtensionNode.CreateInstance() 659 /// method for each node (or TypeExtensionNode.GetInstance() if 660 /// reuseCachedInstance is set to true). 661 /// 662 /// An InvalidOperationException exception is thrown if one of the found 663 /// objects is not a subclass of the provided type. 664 /// </remarks> GetExtensionObjects(string path, Type arrayElementType, bool reuseCachedInstance)665 public object[] GetExtensionObjects (string path, Type arrayElementType, bool reuseCachedInstance) 666 { 667 ExtensionNode node = GetExtensionNode (path); 668 if (node == null) 669 throw new InvalidOperationException ("Extension node not found in path: " + path); 670 return node.GetChildObjects (arrayElementType, reuseCachedInstance); 671 } 672 673 /// <summary> 674 /// Register a listener of extension node changes. 675 /// </summary> 676 /// <param name="path"> 677 /// Path of the node. 678 /// </param> 679 /// <param name="handler"> 680 /// A handler method. 681 /// </param> 682 /// <remarks> 683 /// Hosts can call this method to be subscribed to an extension change 684 /// event for a specific path. The event will be fired once for every 685 /// individual node change. The event arguments include the change type 686 /// (Add or Remove) and the extension node added or removed. 687 /// 688 /// NOTE: The handler will be called for all nodes existing in the path at the moment of registration. 689 /// </remarks> AddExtensionNodeHandler(string path, ExtensionNodeEventHandler handler)690 public void AddExtensionNodeHandler (string path, ExtensionNodeEventHandler handler) 691 { 692 ExtensionNode node = GetExtensionNode (path); 693 if (node == null) 694 throw new InvalidOperationException ("Extension node not found in path: " + path); 695 node.ExtensionNodeChanged += handler; 696 } 697 698 /// <summary> 699 /// Unregister a listener of extension node changes. 700 /// </summary> 701 /// <param name="path"> 702 /// Path of the node. 703 /// </param> 704 /// <param name="handler"> 705 /// A handler method. 706 /// </param> 707 /// <remarks> 708 /// This method unregisters a delegate from the node change event of a path. 709 /// </remarks> RemoveExtensionNodeHandler(string path, ExtensionNodeEventHandler handler)710 public void RemoveExtensionNodeHandler (string path, ExtensionNodeEventHandler handler) 711 { 712 ExtensionNode node = GetExtensionNode (path); 713 if (node == null) 714 throw new InvalidOperationException ("Extension node not found in path: " + path); 715 node.ExtensionNodeChanged -= handler; 716 } 717 718 /// <summary> 719 /// Register a listener of extension node changes. 720 /// </summary> 721 /// <param name="instanceType"> 722 /// Type defining the extension point 723 /// </param> 724 /// <param name="handler"> 725 /// A handler method. 726 /// </param> 727 /// <remarks> 728 /// Hosts can call this method to be subscribed to an extension change 729 /// event for a specific type extension point. The event will be fired once for every 730 /// individual node change. The event arguments include the change type 731 /// (Add or Remove) and the extension node added or removed. 732 /// 733 /// NOTE: The handler will be called for all nodes existing in the path at the moment of registration. 734 /// </remarks> AddExtensionNodeHandler(Type instanceType, ExtensionNodeEventHandler handler)735 public void AddExtensionNodeHandler (Type instanceType, ExtensionNodeEventHandler handler) 736 { 737 string path = AddinEngine.GetAutoTypeExtensionPoint (instanceType); 738 if (path == null) 739 throw new InvalidOperationException ("Type '" + instanceType + "' not bound to an extension point."); 740 AddExtensionNodeHandler (path, handler); 741 } 742 743 /// <summary> 744 /// Unregister a listener of extension node changes. 745 /// </summary> 746 /// <param name="instanceType"> 747 /// Type defining the extension point 748 /// </param> 749 /// <param name="handler"> 750 /// A handler method. 751 /// </param> RemoveExtensionNodeHandler(Type instanceType, ExtensionNodeEventHandler handler)752 public void RemoveExtensionNodeHandler (Type instanceType, ExtensionNodeEventHandler handler) 753 { 754 string path = AddinEngine.GetAutoTypeExtensionPoint (instanceType); 755 if (path == null) 756 throw new InvalidOperationException ("Type '" + instanceType + "' not bound to an extension point."); 757 RemoveExtensionNodeHandler (path, handler); 758 } 759 OnConditionChanged(object s, EventArgs a)760 void OnConditionChanged (object s, EventArgs a) 761 { 762 ConditionType cond = (ConditionType) s; 763 NotifyConditionChanged (cond); 764 } 765 NotifyConditionChanged(ConditionType cond)766 internal void NotifyConditionChanged (ConditionType cond) 767 { 768 try { 769 fireEvents = true; 770 771 ConditionInfo info = (ConditionInfo) conditionTypes [cond.Id]; 772 if (info != null && info.BoundConditions != null) { 773 Hashtable parentsToNotify = new Hashtable (); 774 foreach (BaseCondition c in info.BoundConditions) { 775 ArrayList nodeList = (ArrayList) conditionsToNodes [c]; 776 if (nodeList != null) { 777 foreach (TreeNode node in nodeList) 778 parentsToNotify [node.Parent] = null; 779 } 780 } 781 foreach (TreeNode node in parentsToNotify.Keys) { 782 if (node.NotifyChildrenChanged ()) 783 NotifyExtensionsChanged (new ExtensionEventArgs (node.GetPath ())); 784 } 785 } 786 } 787 finally { 788 fireEvents = false; 789 } 790 791 // Notify child contexts 792 lock (conditionTypes) { 793 if (childContexts != null) { 794 CleanDisposedChildContexts (); 795 foreach (WeakReference wref in childContexts) { 796 ExtensionContext ctx = wref.Target as ExtensionContext; 797 if (ctx != null) 798 ctx.NotifyConditionChanged (cond); 799 } 800 } 801 } 802 } 803 804 NotifyExtensionsChanged(ExtensionEventArgs args)805 internal void NotifyExtensionsChanged (ExtensionEventArgs args) 806 { 807 if (!fireEvents) 808 return; 809 810 if (ExtensionChanged != null) 811 ExtensionChanged (this, args); 812 } 813 NotifyAddinLoaded(RuntimeAddin ad)814 internal void NotifyAddinLoaded (RuntimeAddin ad) 815 { 816 tree.NotifyAddinLoaded (ad, true); 817 818 lock (conditionTypes) { 819 if (childContexts != null) { 820 CleanDisposedChildContexts (); 821 foreach (WeakReference wref in childContexts) { 822 ExtensionContext ctx = wref.Target as ExtensionContext; 823 if (ctx != null) 824 ctx.NotifyAddinLoaded (ad); 825 } 826 } 827 } 828 } 829 CreateExtensionPoint(ExtensionPoint ep)830 internal void CreateExtensionPoint (ExtensionPoint ep) 831 { 832 TreeNode node = tree.GetNode (ep.Path, true); 833 if (node.ExtensionPoint == null) { 834 node.ExtensionPoint = ep; 835 node.ExtensionNodeSet = ep.NodeSet; 836 } 837 } 838 ActivateAddinExtensions(string id)839 internal void ActivateAddinExtensions (string id) 840 { 841 // Looks for loaded extension points which are extended by the provided 842 // add-in, and adds the new nodes 843 844 try { 845 fireEvents = true; 846 847 Addin addin = AddinEngine.Registry.GetAddin (id); 848 if (addin == null) { 849 AddinEngine.ReportError ("Required add-in not found", id, null, false); 850 return; 851 } 852 // Take note that this add-in has been enabled at run-time 853 // Needed because loaded add-in descriptions may not include this add-in. 854 RegisterRuntimeEnabledAddin (id); 855 856 // Look for loaded extension points 857 Hashtable eps = new Hashtable (); 858 ArrayList newExtensions = new ArrayList (); 859 foreach (ModuleDescription mod in addin.Description.AllModules) { 860 foreach (Extension ext in mod.Extensions) { 861 if (!newExtensions.Contains (ext.Path)) 862 newExtensions.Add (ext.Path); 863 ExtensionPoint ep = tree.FindLoadedExtensionPoint (ext.Path); 864 if (ep != null && !eps.Contains (ep)) 865 eps.Add (ep, ep); 866 } 867 } 868 869 // Add the new nodes 870 ArrayList loadedNodes = new ArrayList (); 871 foreach (ExtensionPoint ep in eps.Keys) { 872 ExtensionLoadData data = GetAddinExtensions (id, ep); 873 if (data != null) { 874 foreach (Extension ext in data.Extensions) { 875 TreeNode node = GetNode (ext.Path); 876 if (node != null && node.ExtensionNodeSet != null) { 877 if (node.ChildrenLoaded) 878 LoadModuleExtensionNodes (ext, data.AddinId, node.ExtensionNodeSet, loadedNodes); 879 } 880 else 881 AddinEngine.ReportError ("Extension node not found or not extensible: " + ext.Path, id, null, false); 882 } 883 } 884 } 885 886 // Call the OnAddinLoaded method on nodes, if the add-in is already loaded 887 foreach (TreeNode nod in loadedNodes) 888 nod.ExtensionNode.OnAddinLoaded (); 889 890 // Global extension change event. Other events are fired by LoadModuleExtensionNodes. 891 // The event is called for all extensions, even for those not loaded. This is for coherence, 892 // although that something that it doesn't make much sense to do (subscribing the ExtensionChanged 893 // event without first getting the list of nodes that may change). 894 foreach (string newExt in newExtensions) 895 NotifyExtensionsChanged (new ExtensionEventArgs (newExt)); 896 } 897 finally { 898 fireEvents = false; 899 } 900 // Do the same in child contexts 901 902 lock (conditionTypes) { 903 if (childContexts != null) { 904 CleanDisposedChildContexts (); 905 foreach (WeakReference wref in childContexts) { 906 ExtensionContext ctx = wref.Target as ExtensionContext; 907 if (ctx != null) 908 ctx.ActivateAddinExtensions (id); 909 } 910 } 911 } 912 } 913 RemoveAddinExtensions(string id)914 internal void RemoveAddinExtensions (string id) 915 { 916 try { 917 // Registers this add-in as disabled, so from now on extension from this 918 // add-in will be ignored 919 RegisterRuntimeDisabledAddin (id); 920 921 fireEvents = true; 922 923 // This method removes all extension nodes added by the add-in 924 // Get all nodes created by the addin 925 ArrayList list = new ArrayList (); 926 tree.FindAddinNodes (id, list); 927 928 // Remove each node and notify the change 929 foreach (TreeNode node in list) { 930 if (node.ExtensionNode == null) { 931 // It's an extension point. Just remove it, no notifications are needed 932 node.Remove (); 933 } 934 else { 935 node.ExtensionNode.OnAddinUnloaded (); 936 node.Remove (); 937 } 938 } 939 940 // Notify global extension point changes. 941 // The event is called for all extensions, even for those not loaded. This is for coherence, 942 // although that something that it doesn't make much sense to do (subscribing the ExtensionChanged 943 // event without first getting the list of nodes that may change). 944 945 // We get the runtime add-in because the add-in may already have been deleted from the registry 946 RuntimeAddin addin = AddinEngine.GetAddin (id); 947 if (addin != null) { 948 ArrayList paths = new ArrayList (); 949 // Using addin.Module.ParentAddinDescription here because addin.Addin.Description may not 950 // have a valid reference (the description is lazy loaded and may already have been removed from the registry) 951 foreach (ModuleDescription mod in addin.Module.ParentAddinDescription.AllModules) { 952 foreach (Extension ext in mod.Extensions) { 953 if (!paths.Contains (ext.Path)) 954 paths.Add (ext.Path); 955 } 956 } 957 foreach (string path in paths) 958 NotifyExtensionsChanged (new ExtensionEventArgs (path)); 959 } 960 } finally { 961 fireEvents = false; 962 } 963 } 964 RegisterRuntimeDisabledAddin(string addinId)965 void RegisterRuntimeDisabledAddin (string addinId) 966 { 967 if (runTimeDisabledAddins == null) 968 runTimeDisabledAddins = new ArrayList (); 969 if (!runTimeDisabledAddins.Contains (addinId)) 970 runTimeDisabledAddins.Add (addinId); 971 972 if (runTimeEnabledAddins != null) 973 runTimeEnabledAddins.Remove (addinId); 974 } 975 RegisterRuntimeEnabledAddin(string addinId)976 void RegisterRuntimeEnabledAddin (string addinId) 977 { 978 if (runTimeEnabledAddins == null) 979 runTimeEnabledAddins = new ArrayList (); 980 if (!runTimeEnabledAddins.Contains (addinId)) 981 runTimeEnabledAddins.Add (addinId); 982 983 if (runTimeDisabledAddins != null) 984 runTimeDisabledAddins.Remove (addinId); 985 } 986 GetAddinsForPath(string path, List<string> col)987 internal ICollection GetAddinsForPath (string path, List<string> col) 988 { 989 ArrayList newlist = null; 990 991 // Always consider add-ins which have been enabled at runtime since 992 // they may contain extension for this path. 993 // Ignore addins disabled at run-time. 994 995 if (runTimeEnabledAddins != null && runTimeEnabledAddins.Count > 0) { 996 newlist = new ArrayList (); 997 newlist.AddRange (col); 998 foreach (string s in runTimeEnabledAddins) 999 if (!newlist.Contains (s)) 1000 newlist.Add (s); 1001 } 1002 1003 if (runTimeDisabledAddins != null && runTimeDisabledAddins.Count > 0) { 1004 if (newlist == null) { 1005 newlist = new ArrayList (); 1006 newlist.AddRange (col); 1007 } 1008 foreach (string s in runTimeDisabledAddins) 1009 newlist.Remove (s); 1010 } 1011 1012 return newlist != null ? (ICollection)newlist : (ICollection)col; 1013 } 1014 1015 // Load the extension nodes at the specified path. If the path 1016 // contains extension nodes implemented in an add-in which is 1017 // not loaded, the add-in will be automatically loaded 1018 LoadExtensions(string requestedExtensionPath)1019 internal void LoadExtensions (string requestedExtensionPath) 1020 { 1021 TreeNode node = GetNode (requestedExtensionPath); 1022 if (node == null) 1023 throw new InvalidOperationException ("Extension point not defined: " + requestedExtensionPath); 1024 1025 ExtensionPoint ep = node.ExtensionPoint; 1026 1027 if (ep != null) { 1028 1029 // Collect extensions to be loaded from add-ins. Before loading the extensions, 1030 // they must be sorted, that's why loading is split in two steps (collecting + loading). 1031 1032 ArrayList loadData = new ArrayList (); 1033 1034 foreach (string addin in GetAddinsForPath (ep.Path, ep.Addins)) { 1035 ExtensionLoadData ed = GetAddinExtensions (addin, ep); 1036 if (ed != null) { 1037 // Insert the addin data taking into account dependencies. 1038 // An add-in must be processed after all its dependencies. 1039 bool added = false; 1040 for (int n=0; n<loadData.Count; n++) { 1041 ExtensionLoadData other = (ExtensionLoadData) loadData [n]; 1042 if (AddinEngine.Registry.AddinDependsOn (other.AddinId, ed.AddinId)) { 1043 loadData.Insert (n, ed); 1044 added = true; 1045 break; 1046 } 1047 } 1048 if (!added) 1049 loadData.Add (ed); 1050 } 1051 } 1052 1053 // Now load the extensions 1054 1055 ArrayList loadedNodes = new ArrayList (); 1056 foreach (ExtensionLoadData data in loadData) { 1057 foreach (Extension ext in data.Extensions) { 1058 TreeNode cnode = GetNode (ext.Path); 1059 if (cnode != null && cnode.ExtensionNodeSet != null) 1060 LoadModuleExtensionNodes (ext, data.AddinId, cnode.ExtensionNodeSet, loadedNodes); 1061 else 1062 AddinEngine.ReportError ("Extension node not found or not extensible: " + ext.Path, data.AddinId, null, false); 1063 } 1064 } 1065 // Call the OnAddinLoaded method on nodes, if the add-in is already loaded 1066 foreach (TreeNode nod in loadedNodes) 1067 nod.ExtensionNode.OnAddinLoaded (); 1068 1069 NotifyExtensionsChanged (new ExtensionEventArgs (requestedExtensionPath)); 1070 } 1071 } 1072 GetAddinExtensions(string id, ExtensionPoint ep)1073 ExtensionLoadData GetAddinExtensions (string id, ExtensionPoint ep) 1074 { 1075 Addin pinfo = null; 1076 1077 // Root add-ins are not returned by GetInstalledAddin. 1078 RuntimeAddin addin = AddinEngine.GetAddin (id); 1079 if (addin != null) 1080 pinfo = addin.Addin; 1081 else 1082 pinfo = AddinEngine.Registry.GetAddin (id); 1083 1084 if (pinfo == null) { 1085 AddinEngine.ReportError ("Required add-in not found", id, null, false); 1086 return null; 1087 } 1088 if (!pinfo.Enabled || pinfo.Version != Addin.GetIdVersion (id)) 1089 return null; 1090 1091 // Loads extensions defined in each module 1092 1093 ExtensionLoadData data = null; 1094 AddinDescription conf = pinfo.Description; 1095 GetAddinExtensions (conf.MainModule, id, ep, ref data); 1096 1097 foreach (ModuleDescription module in conf.OptionalModules) { 1098 if (CheckOptionalAddinDependencies (conf, module)) 1099 GetAddinExtensions (module, id, ep, ref data); 1100 } 1101 if (data != null) 1102 data.Extensions.Sort (); 1103 1104 return data; 1105 } 1106 GetAddinExtensions(ModuleDescription module, string addinId, ExtensionPoint ep, ref ExtensionLoadData data)1107 void GetAddinExtensions (ModuleDescription module, string addinId, ExtensionPoint ep, ref ExtensionLoadData data) 1108 { 1109 string basePath = ep.Path + "/"; 1110 1111 foreach (Extension extension in module.Extensions) { 1112 if (extension.Path == ep.Path || extension.Path.StartsWith (basePath)) { 1113 if (data == null) { 1114 data = new ExtensionLoadData (); 1115 data.AddinId = addinId; 1116 data.Extensions = new ArrayList (); 1117 } 1118 data.Extensions.Add (extension); 1119 } 1120 } 1121 } 1122 LoadModuleExtensionNodes(Extension extension, string addinId, ExtensionNodeSet nset, ArrayList loadedNodes)1123 void LoadModuleExtensionNodes (Extension extension, string addinId, ExtensionNodeSet nset, ArrayList loadedNodes) 1124 { 1125 // Now load the extensions 1126 ArrayList addedNodes = new ArrayList (); 1127 tree.LoadExtension (addinId, extension, addedNodes); 1128 1129 RuntimeAddin ad = AddinEngine.GetAddin (addinId); 1130 if (ad != null) { 1131 foreach (TreeNode nod in addedNodes) { 1132 // Don't call OnAddinLoaded here. Do it when the entire extension point has been loaded. 1133 if (nod.ExtensionNode != null) 1134 loadedNodes.Add (nod); 1135 } 1136 } 1137 } 1138 CheckOptionalAddinDependencies(AddinDescription conf, ModuleDescription module)1139 bool CheckOptionalAddinDependencies (AddinDescription conf, ModuleDescription module) 1140 { 1141 foreach (Dependency dep in module.Dependencies) { 1142 AddinDependency pdep = dep as AddinDependency; 1143 if (pdep != null) { 1144 Addin pinfo = AddinEngine.Registry.GetAddin (Addin.GetFullId (conf.Namespace, pdep.AddinId, pdep.Version)); 1145 if (pinfo == null || !pinfo.Enabled) 1146 return false; 1147 } 1148 } 1149 return true; 1150 } 1151 1152 GetNode(string path)1153 TreeNode GetNode (string path) 1154 { 1155 TreeNode node = tree.GetNode (path); 1156 if (node != null || parentContext == null) 1157 return node; 1158 1159 TreeNode supNode = parentContext.tree.GetNode (path); 1160 if (supNode == null) 1161 return null; 1162 1163 if (path.StartsWith ("/")) 1164 path = path.Substring (1); 1165 1166 string[] parts = path.Split ('/'); 1167 TreeNode srcNode = parentContext.tree; 1168 TreeNode dstNode = tree; 1169 1170 foreach (string part in parts) { 1171 1172 // Look for the node in the source tree 1173 1174 int i = srcNode.Children.IndexOfNode (part); 1175 if (i != -1) 1176 srcNode = srcNode.Children [i]; 1177 else 1178 return null; 1179 1180 // Now get the node in the target tree 1181 1182 int j = dstNode.Children.IndexOfNode (part); 1183 if (j != -1) { 1184 dstNode = dstNode.Children [j]; 1185 } 1186 else { 1187 // Create if not found 1188 TreeNode newNode = new TreeNode (AddinEngine, part); 1189 dstNode.AddChildNode (newNode); 1190 dstNode = newNode; 1191 1192 // Copy extension data 1193 dstNode.ExtensionNodeSet = srcNode.ExtensionNodeSet; 1194 dstNode.ExtensionPoint = srcNode.ExtensionPoint; 1195 dstNode.Condition = srcNode.Condition; 1196 1197 if (dstNode.Condition != null) 1198 RegisterNodeCondition (dstNode, dstNode.Condition); 1199 } 1200 } 1201 1202 return dstNode; 1203 } 1204 FindExtensionPathByType(IProgressStatus monitor, Type type, string nodeName, out string path, out string pathNodeName)1205 internal bool FindExtensionPathByType (IProgressStatus monitor, Type type, string nodeName, out string path, out string pathNodeName) 1206 { 1207 return tree.FindExtensionPathByType (monitor, type, nodeName, out path, out pathNodeName); 1208 } 1209 } 1210 1211 class ConditionInfo 1212 { 1213 public object CondType; 1214 public ArrayList BoundConditions; 1215 } 1216 1217 1218 /// <summary> 1219 /// Delegate to be used in extension point subscriptions 1220 /// </summary> ExtensionEventHandler(object sender, ExtensionEventArgs args)1221 public delegate void ExtensionEventHandler (object sender, ExtensionEventArgs args); 1222 1223 /// <summary> 1224 /// Delegate to be used in extension point subscriptions 1225 /// </summary> ExtensionNodeEventHandler(object sender, ExtensionNodeEventArgs args)1226 public delegate void ExtensionNodeEventHandler (object sender, ExtensionNodeEventArgs args); 1227 1228 /// <summary> 1229 /// Arguments for extension events. 1230 /// </summary> 1231 public class ExtensionEventArgs: EventArgs 1232 { 1233 string path; 1234 ExtensionEventArgs()1235 internal ExtensionEventArgs () 1236 { 1237 } 1238 1239 /// <summary> 1240 /// Creates a new instance. 1241 /// </summary> 1242 /// <param name="path"> 1243 /// Path of the extension node that has changed. 1244 /// </param> ExtensionEventArgs(string path)1245 public ExtensionEventArgs (string path) 1246 { 1247 this.path = path; 1248 } 1249 1250 /// <summary> 1251 /// Path of the extension node that has changed. 1252 /// </summary> 1253 public virtual string Path { 1254 get { return path; } 1255 } 1256 1257 /// <summary> 1258 /// Checks if a path has changed. 1259 /// </summary> 1260 /// <param name="pathToCheck"> 1261 /// An extension path. 1262 /// </param> 1263 /// <returns> 1264 /// 'true' if the path is affected by the extension change event. 1265 /// </returns> 1266 /// <remarks> 1267 /// Checks if the specified path or any of its children paths is affected by the extension change event. 1268 /// </remarks> PathChanged(string pathToCheck)1269 public bool PathChanged (string pathToCheck) 1270 { 1271 if (pathToCheck.EndsWith ("/")) 1272 return path.StartsWith (pathToCheck); 1273 else 1274 return path.StartsWith (pathToCheck) && (pathToCheck.Length == path.Length || path [pathToCheck.Length] == '/'); 1275 } 1276 } 1277 1278 /// <summary> 1279 /// Arguments for extension node events. 1280 /// </summary> 1281 public class ExtensionNodeEventArgs: ExtensionEventArgs 1282 { 1283 ExtensionNode node; 1284 ExtensionChange change; 1285 1286 /// <summary> 1287 /// Creates a new instance 1288 /// </summary> 1289 /// <param name="change"> 1290 /// Type of change. 1291 /// </param> 1292 /// <param name="node"> 1293 /// Node that has been added or removed. 1294 /// </param> ExtensionNodeEventArgs(ExtensionChange change, ExtensionNode node)1295 public ExtensionNodeEventArgs (ExtensionChange change, ExtensionNode node) 1296 { 1297 this.node = node; 1298 this.change = change; 1299 } 1300 1301 /// <summary> 1302 /// Path of the extension that changed. 1303 /// </summary> 1304 public override string Path { 1305 get { return node.Path; } 1306 } 1307 1308 /// <summary> 1309 /// Type of change. 1310 /// </summary> 1311 public ExtensionChange Change { 1312 get { return change; } 1313 } 1314 1315 /// <summary> 1316 /// Node that has been added or removed. 1317 /// </summary> 1318 public ExtensionNode ExtensionNode { 1319 get { return node; } 1320 } 1321 1322 /// <summary> 1323 /// Extension object that has been added or removed. 1324 /// </summary> 1325 public object ExtensionObject { 1326 get { 1327 InstanceExtensionNode tnode = node as InstanceExtensionNode; 1328 if (tnode == null) 1329 throw new InvalidOperationException ("Node is not an InstanceExtensionNode"); 1330 return tnode.GetInstance (); 1331 } 1332 } 1333 } 1334 1335 /// <summary> 1336 /// Type of change in an extension change event. 1337 /// </summary> 1338 public enum ExtensionChange 1339 { 1340 /// <summary> 1341 /// An extension node has been added. 1342 /// </summary> 1343 Add, 1344 1345 /// <summary> 1346 /// An extension node has been removed. 1347 /// </summary> 1348 Remove 1349 } 1350 1351 1352 internal class ExtensionLoadData 1353 { 1354 public string AddinId; 1355 public ArrayList Extensions; 1356 } 1357 } 1358