Monday, April 22, 2013

Re-using core expressions for own extension points

Most of us use the expression framework to enable/disable handlers or to display/hide UI elements by providing enabledWhen or visibleWhen statements. Recently I needed to define an enablement for a custom extension point and found it appealing to reuse the expression framework for that purpose.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.
 
Step 1: Create an extension point

Create a new Plug-in project com.example.expressions. Switch to the Extension Points tab and create a new extension point by clicking the Add... button.

Set Extension Point ID to sample, Extension Point Name to Sample and hit Finish. We end up in the extension point schema editor where we switch to the Definition tab.

Create a baseElement and an expression element (hit New Element for that purpose). The baseElement contains a sequence (or choice, doesn't matter) of expressions. Make sure that exactly one expression can be defined by setting Max Occurrences to 1. If you set Min Occurrences to 0 the expression is optional, otherwise it is mandatory.

An expression is constructed by elements of certain types. When you are familiar with enabledWhen, visibleWhen, ... you are already familiar with these. They are defined in another scheme which we need to reference from our custom theme. Unless bug 384001 is fixed we need to do this in the Source view of our extension. Add following line within the schema node of your extension source:
<include schemaLocation="schema://org.eclipse.core.expressions/schema/expressionLanguage.exsd"/>
Now switch back to the Definition tab and add a choice of following elements to the expression element:

not, or, and, instanceof, test, systemTest, equals, count, with, resolve, adapt, iterate, reference.

Finally add a choice (or sequence) of baseElements to the extension element. Your assembled definition should look like this:


Here is the xml definition for your reference:
<?xml version='1.0' encoding='UTF-8'?>
<!-- Schema file written by PDE -->
<schema targetNamespace="com.example.expressions" xmlns="http://www.w3.org/2001/XMLSchema">
<annotation>
      <appinfo>
         <meta.schema plugin="com.example.expressions" id="sample" name="Sample"/>
      </appinfo>
      <documentation>
         [Enter description of this extension point.]
      </documentation>
   </annotation>

   <include schemaLocation="schema://org.eclipse.core.expressions/schema/expressionLanguage.exsd"/>

   <element name="extension">
      <annotation>
         <appinfo>
            <meta.element />
         </appinfo>
      </annotation>
      <complexType>
         <choice minOccurs="1" maxOccurs="unbounded">
            <element ref="baseElement"/>
         </choice>
         <attribute name="point" type="string" use="required">
            <annotation>
               <documentation>
                  
               </documentation>
            </annotation>
         </attribute>
         <attribute name="id" type="string">
            <annotation>
               <documentation>
                  
               </documentation>
            </annotation>
         </attribute>
         <attribute name="name" type="string">
            <annotation>
               <documentation>
                  
               </documentation>
               <appinfo>
                  <meta.attribute translatable="true"/>
               </appinfo>
            </annotation>
         </attribute>
      </complexType>
   </element>

   <element name="baseElement">
      <complexType>
         <sequence>
            <element ref="expression"/>
         </sequence>
      </complexType>
   </element>

   <element name="expression">
      <complexType>
         <choice>
            <element ref="not"/>
            <element ref="or"/>
            <element ref="and"/>
            <element ref="instanceof"/>
            <element ref="test"/>
            <element ref="systemTest"/>
            <element ref="equals"/>
            <element ref="count"/>
            <element ref="with"/>
            <element ref="resolve"/>
            <element ref="adapt"/>
            <element ref="iterate"/>
            <element ref="reference"/>
         </choice>
      </complexType>
   </element>

   <annotation>
      <appinfo>
         <meta.section type="since"/>
      </appinfo>
      <documentation>
         [Enter the first release in which this extension point appears.]
      </documentation>
   </annotation>

   <annotation>
      <appinfo>
         <meta.section type="examples"/>
      </appinfo>
      <documentation>
         [Enter extension point usage example here.]
      </documentation>
   </annotation>

   <annotation>
      <appinfo>
         <meta.section type="apiinfo"/>
      </appinfo>
      <documentation>
         [Enter API information here.]
      </documentation>
   </annotation>

   <annotation>
      <appinfo>
         <meta.section type="implementation"/>
      </appinfo>
      <documentation>
         [Enter information about supplied implementation of this extension point.]
      </documentation>
   </annotation>
</schema>

Step 2: Using the extension point

Go back to the plugin.xml of com.example.expressions and switch to the Extensions tab. Add a new extension of type com.example.expressions.sample. Now add a baseElement to it and fill the expression child node with some useful content:


This simple expression example expects the current selection to be empty.

I am using the org.eclipse.ui.startup extension point to create some sample test code to evaluate the expression.

Step 3: Create Java Expression from plugin definition

When parsing extension points data to java code, we need to convert the expression definition to a java org.eclipse.core.expressions.Expression instance. The ExpressionConverter class will do that for us:
private static final String PLUGIN_ID = "com.example.expressions";
private static final String EXTENSION_POINT = "sample";
private static final String NODE_EXPRESSION = "expression";

public Expression loadExpression() {
 IConfigurationElement[] elements = Platform.getExtensionRegistry().getExtensionPoint(PLUGIN_ID, EXTENSION_POINT).getConfigurationElements();
 for (IConfigurationElement element : elements) {

  // find expression nodes
  final IConfigurationElement[] children = element.getChildren(NODE_EXPRESSION);
  if (children.length == 1) {
   // we expect exactly 1 expression node...

   final IConfigurationElement[] expressionRoot = children[0].getChildren();
   if (expressionRoot.length > 0) {
    // ... containing exactly one root entry
    try {
     return ExpressionConverter.getDefault().perform(expressionRoot[0]);

    } catch (final CoreException e1) {
    }
   }
  }
 }

 return null;
}

Step 4: Evaluating an expression

For evaluation an expression needs an IEvaluationContext. A context contains variables (which can be used by a with node of an expression) to be evaluated. If you do not need variables you can simply create a new EvaluationContext and add your own variables to it:
EvaluationContext context = new EvaluationContext(null, new Object());
context.addVariable("someName","some arbitrary content");
EvaluationContext takes as first argument a base context (which may be null) and as second argument a default variable to perform the test upon (used when no when node exists in the expression).
You can also use the default context used by the workbench. Get it from the IHandlerService:
IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);
IEvaluationContext currentState = service.getCurrentState();
Now evaluating the expression works as follows:
public Object evaluateExpression(Expression expression) throws CoreException {
 IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);
 IEvaluationContext currentState = service.getCurrentState();

 return expression.evaluate(currentState);
}
The sample project contains running code to see this expression in action.

References

Eclipse API documentation

No comments:

Post a Comment