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

Thursday, April 4, 2013

Adding JavaDoc to help system

Sometimes it is useful to provide JavaDoc documentation to the user. While adding such documentation to an RCP application might be a special case it could make more sense to have such documentation ready for Eclipse extensions. If you combine this with custom doclets you might generate even more than just API documentation.

Instead of adding external html files we will add this documentation to the Eclipse help system.

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 JavaDoc

First lets create a place to store our generated files in. So create a new Plug-in Project called com.example.javadoc.

Now select the menu entry Project / Generate Javadoc....

You need to define the javadoc executable, the source files to be parsed and the destination where to create html files. We will set the latter to com.example.javadoc/javadoc.


On the last wizard page you have the option to save all settings to an ant script so you can regenerate your documentation easily.

 

After finishing the wizard you are asked whether to update the javadoc location or not. Say No To All here.

When you refresh your project in the navigator you should see the generated files.

Step 2: Optimize build process

Refreshing by hand is nasty, why not let Eclipse do the job for us? Select your generate_javadoc.xml, right click and select Run As / Ant Build ... On the Refresh tab you can adjust which resources to refresh after the build process.

Step 3: Adding files to Eclipse help

There exist lots of tutorials on how to add help content to eclipse. As a starting point take a look at Contributing a little help or Lars' tutorial on Adding Help to RCP Applications.

We will create a book with a single section containing our help content. Create the book by adding a new file help/book.xml to our project:
<?xml version="1.0" encoding="UTF-8"?>
<toc label="Sample Book">
 <topic label="API Documentation">
  <link toc="help/API_documentation.xml" />
 </topic>
</toc>
Now add another file help/API_documentation.xml:
<?xml version="1.0" encoding="UTF-8"?>
<toc label="Sample API">
 <topic label="JavaDoc" href="javadoc/index.html" />
</toc>

You can open/edit these files with the Table of Contents Editor, which gives you a nicer UI to create such files, but usually the XML is simple enough to remain in the text editor.

To add these files to Eclipse help we need to use extension points. Open your MANIFEST.MF file and switch to the Extensions tab. Create a new extension for org.eclipse.help.toc and create two toc child nodes. Each of them refers to one of our xml files. To create a new book (which is displayed as a root level element in the help contents) we need to set primary to true.


The final plugin.xml should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.help.toc">
      <toc
            file="help/book.xml"
            primary="true">
      </toc>
      <toc
            file="help/API_documentation.xml"
            primary="false">
      </toc>
   </extension>
</plugin>
Finally switch to the Build tab and make sure help and javadoc folders are included in the Binary Build.

Run your application and select Help / Help Contents to see our new book in action.

Optional: Open a help page programmatically

The help system allows to open dedicated pages on demand. You can open an html page by calling
PlatformUI.getWorkbench().getHelpSystem().displayHelpResource("/<plugin ID>/path/to/your/file.html");

Further improvements

Currently the creation of JavaDoc is still a manual process. But we could combine this with the Custom Builder tutorial and let the builder run javadoc. Then you could add the builder to all your projects that contribute to your javadoc files.