7
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
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:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
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.
31
using System.Collections;
32
using System.Collections.Generic;
34
using System.Reflection;
35
using Mono.Addins.Description;
40
/// A node of the extension model.
43
/// An extension node is an element registered by an add-in in an extension point.
44
/// A host can get nodes registered in an extension point using methods such as
45
/// AddinManager.GetExtensionNodes(string), which returns a collection of ExtensionNode objects.
47
/// ExtensionNode will normally be used as a base class of more complex extension point types.
48
/// The most common subclass is Mono.Addins.TypeExtensionNode, which allows registering a class
49
/// implemented in an add-in.
51
public class ExtensionNode
55
ExtensionNodeList childNodes;
58
ExtensionNodeType nodeType;
59
ModuleDescription module;
60
AddinEngine addinEngine;
61
event ExtensionNodeEventHandler extensionNodeChanged;
64
/// Identifier of the node.
67
/// It is not mandatory to specify an 'id' for a node. When none is provided,
68
/// the add-in manager will automatically generate an unique id for the node.
69
/// The ExtensionNode.HasId property can be used to know if the 'id' has been
70
/// specified by the developer or not.
73
get { return treeNode != null ? treeNode.Id : string.Empty; }
77
/// Location of this node in the extension tree.
80
/// The node path is composed by the path of the extension point where it is defined,
81
/// the identifiers of its parent nodes, and its own identifier.
84
get { return treeNode != null ? treeNode.GetPath () : string.Empty; }
88
/// Parent node of this node.
90
public ExtensionNode Parent {
92
if (treeNode != null && treeNode.Parent != null)
93
return treeNode.Parent.ExtensionNode;
100
/// Extension context to which this node belongs
102
public ExtensionContext ExtensionContext {
103
get { return treeNode.Context; }
107
/// Specifies whether the extension node has as an Id or not.
110
/// It is not mandatory to specify an 'id' for a node. When none is provided,
111
/// the add-in manager will automatically generate an unique id for the node.
112
/// This property will return true if an 'id' was provided for the node, and
113
/// false if the id was assigned by the add-in manager.
116
get { return !Id.StartsWith (ExtensionTree.AutoIdPrefix); }
119
internal void SetTreeNode (TreeNode node)
124
internal void SetData (AddinEngine addinEngine, string plugid, ExtensionNodeType nodeType, ModuleDescription module)
126
this.addinEngine = addinEngine;
127
this.addinId = plugid;
128
this.nodeType = nodeType;
129
this.module = module;
132
internal string AddinId {
133
get { return addinId; }
136
internal TreeNode TreeNode {
137
get { return treeNode; }
141
/// The add-in that registered this extension node.
144
/// This property provides access to the resources and types of the add-in that created this extension node.
146
public RuntimeAddin Addin {
148
if (addin == null && addinId != null) {
149
if (!addinEngine.IsAddinLoaded (addinId))
150
addinEngine.LoadAddin (null, addinId, true);
151
addin = addinEngine.GetAddin (addinId);
153
addin = addin.GetModule (module);
156
throw new InvalidOperationException ("Add-in '" + addinId + "' could not be loaded.");
162
/// Notifies that a child node of this node has been added or removed.
165
/// The first time the event is subscribed, the handler will be called for each existing node.
167
public event ExtensionNodeEventHandler ExtensionNodeChanged {
169
extensionNodeChanged += value;
170
foreach (ExtensionNode node in ChildNodes) {
172
value (this, new ExtensionNodeEventArgs (ExtensionChange.Add, node));
173
} catch (Exception ex) {
174
addinEngine.ReportError (null, node.Addin != null ? node.Addin.Id : null, ex, false);
179
extensionNodeChanged -= value;
184
/// Child nodes of this extension node.
186
public ExtensionNodeList ChildNodes {
192
if (treeNode.Children.Count == 0) {
193
childNodes = ExtensionNodeList.Empty;
197
catch (Exception ex) {
198
addinEngine.ReportError (null, null, ex, false);
199
childNodes = ExtensionNodeList.Empty;
202
childrenLoaded = true;
205
List<ExtensionNode> list = new List<ExtensionNode> ();
206
foreach (TreeNode cn in treeNode.Children) {
208
// For each node check if it is visible for the current context.
209
// If something fails while evaluating the condition, just ignore the node.
212
if (cn.ExtensionNode != null && cn.IsEnabled)
213
list.Add (cn.ExtensionNode);
214
} catch (Exception ex) {
215
addinEngine.ReportError (null, null, ex, false);
219
childNodes = new ExtensionNodeList (list);
221
childNodes = ExtensionNodeList.Empty;
228
/// Returns the child objects of a node.
231
/// An array of child objects.
234
/// This method only works if all children of this node are of type Mono.Addins.TypeExtensionNode.
235
/// The returned array is composed by all objects created by calling the
236
/// TypeExtensionNode.GetInstance() method for each node.
238
public object[] GetChildObjects ()
240
return GetChildObjects (typeof(object), true);
244
/// Returns the child objects of a node.
246
/// <param name="reuseCachedInstance">
247
/// True if the method can reuse instances created in previous calls.
250
/// An array of child objects.
253
/// This method only works if all children of this node are of type Mono.Addins.TypeExtensionNode.
254
/// The returned array is composed by all objects created by calling the TypeExtensionNode.CreateInstance()
255
/// method for each node (or TypeExtensionNode.GetInstance() if reuseCachedInstance is set to true).
257
public object[] GetChildObjects (bool reuseCachedInstance)
259
return GetChildObjects (typeof(object), reuseCachedInstance);
263
/// Returns the child objects of a node (with type check).
265
/// <param name="arrayElementType">
266
/// Type of the return array elements.
269
/// An array of child objects.
272
/// This method only works if all children of this node are of type Mono.Addins.TypeExtensionNode.
273
/// The returned array is composed by all objects created by calling the
274
/// TypeExtensionNode.GetInstance(Type) method for each node.
276
/// An InvalidOperationException exception is thrown if one of the found child objects is not a
277
/// subclass of the provided type.
279
public object[] GetChildObjects (Type arrayElementType)
281
return GetChildObjects (arrayElementType, true);
285
/// Returns the child objects of a node (casting to the specified type)
288
/// An array of child objects.
291
/// This method only works if all children of this node are of type Mono.Addins.TypeExtensionNode.
292
/// The returned array is composed by all objects created by calling the
293
/// TypeExtensionNode.GetInstance() method for each node.
295
public T[] GetChildObjects<T> ()
297
return (T[]) GetChildObjectsInternal (typeof(T), true);
300
/// Returns the child objects of a node (with type check).
302
/// <param name="arrayElementType">
303
/// Type of the return array elements.
305
/// <param name="reuseCachedInstance">
306
/// True if the method can reuse instances created in previous calls.
309
/// An array of child objects.
312
/// This method only works if all children of this node are of type Mono.Addins.TypeExtensionNode.
313
/// The returned array is composed by all objects created by calling the TypeExtensionNode.CreateInstance(Type)
314
/// method for each node (or TypeExtensionNode.GetInstance(Type) if reuseCachedInstance is set to true).
316
/// An InvalidOperationException exception will be thrown if one of the found child objects is not a subclass
317
/// of the provided type.
319
public object[] GetChildObjects (Type arrayElementType, bool reuseCachedInstance)
321
return (object[]) GetChildObjectsInternal (arrayElementType, reuseCachedInstance);
325
/// Returns the child objects of a node (casting to the specified type).
327
/// <param name="reuseCachedInstance">
328
/// True if the method can reuse instances created in previous calls.
331
/// An array of child objects.
334
/// This method only works if all children of this node are of type Mono.Addins.TypeExtensionNode.
335
/// The returned array is composed by all objects created by calling the TypeExtensionNode.CreateInstance()
336
/// method for each node (or TypeExtensionNode.GetInstance() if reuseCachedInstance is set to true).
338
public T[] GetChildObjects<T> (bool reuseCachedInstance)
340
return (T[]) GetChildObjectsInternal (typeof(T), reuseCachedInstance);
343
Array GetChildObjectsInternal (Type arrayElementType, bool reuseCachedInstance)
345
ArrayList list = new ArrayList (ChildNodes.Count);
347
for (int n=0; n<ChildNodes.Count; n++) {
348
InstanceExtensionNode node = ChildNodes [n] as InstanceExtensionNode;
350
addinEngine.ReportError ("Error while getting object for node in path '" + Path + "'. Extension node is not a subclass of InstanceExtensionNode.", null, null, false);
355
if (reuseCachedInstance)
356
list.Add (node.GetInstance (arrayElementType));
358
list.Add (node.CreateInstance (arrayElementType));
360
catch (Exception ex) {
361
addinEngine.ReportError ("Error while getting object for node in path '" + Path + "'.", node.AddinId, ex, false);
364
return list.ToArray (arrayElementType);
368
/// Reads the extension node data
370
/// <param name='elem'>
371
/// The element containing the extension data
374
/// This method can be overriden to provide a custom method for reading extension node data from an element.
375
/// The default implementation reads the attributes if the element and assigns the values to the fields
376
/// and properties of the extension node that have the corresponding [NodeAttribute] decoration.
378
internal protected virtual void Read (NodeElement elem)
380
if (nodeType == null)
383
NodeAttribute[] attributes = elem.Attributes;
384
ReadObject (this, attributes, nodeType.Fields);
386
if (nodeType.CustomAttributeMember != null) {
387
object att = Activator.CreateInstance (nodeType.CustomAttributeMember.MemberType);
388
ReadObject (att, attributes, nodeType.CustomAttributeFields);
389
nodeType.CustomAttributeMember.SetValue (this, att);
393
void ReadObject (object ob, NodeAttribute[] attributes, Dictionary<string,ExtensionNodeType.FieldData> fields)
398
// Make a copy because we are going to remove fields that have been used
399
fields = new Dictionary<string,ExtensionNodeType.FieldData> (fields);
401
foreach (NodeAttribute at in attributes) {
403
ExtensionNodeType.FieldData f;
404
if (!fields.TryGetValue (at.name, out f))
407
fields.Remove (at.name);
410
Type memberType = f.MemberType;
412
if (memberType == typeof(string)) {
414
val = Addin.Localizer.GetString (at.value);
418
else if (memberType == typeof(string[])) {
419
string[] ss = at.value.Split (',');
420
if (ss.Length == 0 && ss[0].Length == 0)
421
val = new string [0];
423
for (int n=0; n<ss.Length; n++)
424
ss [n] = ss[n].Trim ();
428
else if (memberType.IsEnum) {
429
val = Enum.Parse (memberType, at.value);
433
val = Convert.ChangeType (at.Value, memberType);
434
} catch (InvalidCastException) {
435
throw new InvalidOperationException ("Property type not supported by [NodeAttribute]: " + f.Member.DeclaringType + "." + f.Member.Name);
439
f.SetValue (ob, val);
442
if (fields.Count > 0) {
443
// Check if one of the remaining fields is mandatory
444
foreach (KeyValuePair<string,ExtensionNodeType.FieldData> e in fields) {
445
ExtensionNodeType.FieldData f = e.Value;
447
throw new InvalidOperationException ("Required attribute '" + e.Key + "' not found.");
452
internal bool NotifyChildChanged ()
457
ExtensionNodeList oldList = childNodes;
458
childrenLoaded = false;
460
bool changed = false;
462
foreach (ExtensionNode nod in oldList) {
463
if (ChildNodes [nod.Id] == null) {
465
OnChildNodeRemoved (nod);
468
foreach (ExtensionNode nod in ChildNodes) {
469
if (oldList [nod.Id] == null) {
471
OnChildNodeAdded (nod);
475
OnChildrenChanged ();
480
/// Called when the add-in that defined this extension node is actually loaded in memory.
482
internal protected virtual void OnAddinLoaded ()
487
/// Called when the add-in that defined this extension node is being
488
/// unloaded from memory.
490
internal protected virtual void OnAddinUnloaded ()
495
/// Called when the children list of this node has changed. It may be due to add-ins
496
/// being loaded/unloaded, or to conditions being changed.
498
protected virtual void OnChildrenChanged ()
503
/// Called when a child node is added
505
/// <param name="node">
508
protected virtual void OnChildNodeAdded (ExtensionNode node)
510
if (extensionNodeChanged != null)
511
extensionNodeChanged (this, new ExtensionNodeEventArgs (ExtensionChange.Add, node));
515
/// Called when a child node is removed
517
/// <param name="node">
520
protected virtual void OnChildNodeRemoved (ExtensionNode node)
522
if (extensionNodeChanged != null)
523
extensionNodeChanged (this, new ExtensionNodeEventArgs (ExtensionChange.Remove, node));
528
/// An extension node with custom metadata
531
/// This is the default type for extension nodes bound to a custom extension attribute.
533
public class ExtensionNode<T>: ExtensionNode where T:CustomExtensionAttribute
538
/// The custom attribute containing the extension metadata
543
internal set { data = value; }