Google
 

Wednesday, June 13, 2007

Implementing the Core of SharpDevelop

The AddIn Tree
The AddIn tree is a simple tree data structure. There is only one AddIn tree for each instance of
SharpDevelop. This is the reason for implementing it by using the singleton design pattern
This diagram will give us the basic idea of add-ins; add-ins just plug into the tree and the tree contains the
whole system. Physically, an add-in is defined by an XML file and a set of DLLs that are referenced by the
XML. The DLLs provide the code, while the XML defines how and where it plugs into the AddIn tree.

The AddIn tree is a tree that "binds them all".The add-ins are the nodes of the AddIn tree
All SharpDevelop parts that use the AddIn tree must know the path from which they can obtain the required
nodes.
In other words, the
add-in that defines a particular path also gets to define the (runtime) interface of the nodes that can be
inserted below it.

All visible elements in
SharpDevelop (and most invisible ones too; for example, keyboard commands in the text editor, like cursor
keys, are implemented as nodes in the add-in tree) are defined by nodes.

Advantages of Using the AddIn Tree

  1. It allows extension of existing add-ins by other add-ins.
  2. Another advantage of the AddIn tree is that the assembly files containing the executable
    code need not reside in one directory.
  3. Also with this approach, the add-ins don't have to implement their own add-in structure, as all
    the add-ins are based on only one system – The AddIn tree.
As per our design principles, SharpDevelop does not embed any GUI layer in the Core, giving a
good extensibility for any further use of other toolkits. The GUI layer is only implemented when is actually needed by the application.

The AddIn Tree Superstructure

public interface IAddInTree
{
// these factories:ConditionFactory and CodonFactory create our AddIn tree node contents.
///
/// Returns the default condition factory.
///
ConditionFactory ConditionFactory
{
get ;
}
///
/// Returns the default codon factory.
///
CodonFactory CodonFactory
{
get ;
}
///
/// Returns a collection of all loaded add-ins.
///
AddInCollection AddIns
{
get ;
}

///
/// Returns a TreeNode corresponding to path .
/// This is the only method needed by the add-ins to use the AddIn tree.
///

///
IAddInTreeNode GetTreeNode(string path);

// The InsertAddIn and RemoveAddIn methods may be useful for implementing an add-in manager in theIDE
///
/// Inserts an AddIn into the AddInTree.
///
///

void InsertAddIn(AddIn addIn);

///
/// Removes an AddIn from the AddInTree.
///
void
RemoveAddIn(AddIn addIn);

///
/// This method does load all codons and conditions in the given assembly.
///
Assembly LoadAssembly(string assemblyFile);
}


Add-in Definition





< AddIn name = "Typed Collection Wizard"
author
= "Mike Krueger"
copyright
= "GPL"
url
= "unknown"
description
= "Creates a typed collection"
version
= "1.0.0">

<Runtime>
<Import assembly="TypedCollectionWizard.dll" />
Runtime>



<Extension path = "/SharpDevelop/Templates/File/TypedCollection" >
<DialogPanel id = "CollectionGenerator"
label
= "Typed Collection"
class
= "TypedCollectionGenerator.TypedCollectionWizardPanel"/>
Extension>
AddIn>


public interface ICodon
{
// returns the add-in in which this codon object was declared
AddIn AddIn
{
get;
set;
}
// returns the name of the xml node of this codon. (it is the same
// for each type of codon (the name of the XML tag inside
// the add-in file))
//Each codon class must have a unique name.
string Name
{
get;
}

///
/// The ID is the name of the codon object inside the tree (codons are referenced by their ID), therefore no two
/// codons can have the same ID when they are stored under the same AddIn tree path. This ID comes from the
/// XML attribute CollectionGenerator of the DialogPanel, which we saw before.
/// returns the ID of this codon object.
///
string ID
{
get;
}
// returns the Class which is used in the action corresponding to
// this codon (may return null, if no action for this codon is
// given)
string Class
{
get;
}
// Insert this codon after all the codons defined in this string
// array
string[] InsertAfter
{
get;
set;
}
// Insert this codon before the codons defined in this string array
string[] InsertBefore
{
get;
}
//The only method that a codon must have is the BuildItem method.
// Creates an item (=object) with the specified sub items and
// the current Condition status for this item.
object BuildItem( object owner, ArrayList subItems,
ConditionFailedAction action);
}

Codons can define additional attributes, the values of which they obtain from the XML definition, and can
apply them to the objects that they build. The AddIn tree puts these build codons together so that they can be used outside the tree structure.

Now we will take a look at the manner in which a codon class flags the add-in system that it has attributes that need to be read from the codon XML node. flagging fields by using attributes.

We use the custom attributes feature of C# to define which attributes a codon has, because we need to make it explicit when a codon has mandatory attributes.

public abstract class AbstractCodon : ICodon
{
[XmlMemberAttributeAttribute(
"id", IsRequired = true)]
string id = null;
[XmlMemberAttributeAttribute(
"class")]
string myClass = null ;
[XmlMemberArrayAttribute(
"insertafter" )]
string[] insertafter = null;
[XmlMemberArrayAttribute(
"insertbefore " )]
string[] insertbefore = null;
// Canonical get/set properties for all attributes seen above are taken out.
// Creates an item with the specified sub items and the current Condition status for this item.
public abstract object BuildItem(object owner, ArrayList subItems, ConditionFailedAction action);
}

Codons have a class attribute, CodonNameAttribute, applied that has only one parameter – the codon name. This is the name of the codon XML node.

From Tree Node to Running Object (74)

public  interface IAddInTreeNode
{
    
// A hash table containing the child nodes.
    Hashtable ChildNodes {
        
get;
    }
    
// A codon defined in this node
    ICodon Codon {
        
get;
    }
    
//  All conditions for this TreeNode.
    ConditionCollection ConditionCollection {
        
get;
    }
    
// Gets the current ConditionFailedAction
    ConditionFailedAction GetCurrentConditionFailedAction(object caller);
    
// Builds all child items of this node using the <code>BuildItem</code>
    
//  method of each codon in the child tree.
    ArrayList BuildChildItems(object  caller);
    
// Builds one child item of this node using the <code>BuildItem</code>
    
// method of the codon in the child tree. The sub item with the ID
     object BuildChildItem(string childItemID, object  caller);
}

The BuildChildItems method calls BuildItem on all child codons (if any) and returns them as an ArrayList.
Note that the subItems are created recursively with this method (called on the child) and that the codon BuildItem method must handle the sub-items itself. Each tree node can have sub-items and during the object creation these sub-items must be handled.

Codon Creation



The system allows new codons to be defined by add-ins. This ensures much more flexibility than defining a static set of codons inside the core assembly. When an assembly is imported by the add-in subsystem, it is scanned for types that have the CodonNameAttribute attached and are a subclass of the AbstractCodon class.

The codon responsible for a specific XML node is determined by the node name. This node name and the codon name, which is given by the CodonNameAttribute, must be equal. The core locates the codon that has a CodonNameAttribute, which matches the XML node name.

Conditions
A condition is used in the AddIn tree to indicate whether a node is active (that is, if it should be built when BuildItem is called). This is useful for dynamically changing the AddIn tree. Dynamic changes are used in the menus to make menu items invisible, when they aren't used or to disable menu items.

Currently there are only three possible options for a condition – Nothing, Exclude, and Disable. These actions occur only when the condition evaluates to false.
  1. Nothing does nothing, if the condition fails. If we choose this, it will be similar to not using a condition. This is only included for the sake of completeness.
  2. If no action attribute is specified, Exclude is the default action.It merely removes the item virtually from the AddIn tree when the BuildItem method is called.
  3. Disable tries to disable the item. The codon object must handle the disable case by itself. Only the object knows whether it can be disabled and how this happens.






Happy day, happy life!

No comments: