Monday, April 16, 2012

Expression Examples

The previous post about the Expression Framework might be a bit confusing. Therefore I will provide some expression examples for eclipse built in source providers and tester and for the custom property tester provided in the previous post.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online. 

Preparations

Create a new Plug-in Project called com.codeandme.coreexpressions.samples. Add dependencies to org.eclipse.ui, org.eclipse.core.runtime and com.codeandme.coreexpressions. Set the content of plugin.xml to
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.ui.commands">
      <command
            defaultHandler="com.codeandme.coreexpressions.samples.Login"
            id="com.codeandme.coreexpressions.commands.login"
            name="login">
      </command>
      <command
            defaultHandler="com.codeandme.coreexpressions.samples.Logout"
            id="com.codeandme.coreexpressions.commands.logout"
            name="logout">
      </command>
   </extension>
</plugin>
Add a new class com.codeandme.coreexpressions.samples.Login
package com.codeandme.coreexpressions.samples;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.ui.ISourceProvider;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.services.ISourceProviderService;

import com.codeandme.coreexpressions.ExampleSourceProvider;

public class Login extends AbstractHandler implements IHandler {

 @Override
 public Object execute(ExecutionEvent event) throws ExecutionException {

  ISourceProviderService service = (ISourceProviderService) PlatformUI.getWorkbench().getService(
    ISourceProviderService.class);
  ISourceProvider provider = service.getSourceProvider(ExampleSourceProvider.CURRENT_USER);
  if (provider instanceof ExampleSourceProvider)
   ((ExampleSourceProvider) provider).setCurrentUser("John Doe");

  return null;
 }
}
and another class com.codeandme.coreexpressions.samples.Logout
package com.codeandme.coreexpressions.samples;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.ui.ISourceProvider;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.services.ISourceProviderService;

import com.codeandme.coreexpressions.ExampleSourceProvider;

public class Logout extends AbstractHandler implements IHandler {

 @Override
 public Object execute(ExecutionEvent event) throws ExecutionException {
  ISourceProviderService service = (ISourceProviderService) PlatformUI.getWorkbench().getService(
    ISourceProviderService.class);
  ISourceProvider provider = service.getSourceProvider(ExampleSourceProvider.CURRENT_USER);
  if (provider instanceof ExampleSourceProvider)
   ((ExampleSourceProvider) provider).setCurrentUser(null);

  return null;
 }
}
We will use these commands in a later step of our tutorial.

The first commands we use will simply bind to the commandId=org.eclipse.ui.file.exit.

Test 1: Enable for a specific view

Our first expression will reveal a toolbar item in the Project Explorer view when the view is active.
      <menuContribution
            allPopups="false"
            locationURI="toolbar:org.eclipse.ui.navigator.ProjectExplorer">
            <command
                  commandId="org.eclipse.ui.file.exit"
                  label="Test1"
                  style="push">
               <visibleWhen
                     checkEnabled="false">
                  <with
                        variable="activePartId">
                     <equals
                           value="org.eclipse.ui.navigator.ProjectExplorer">
                     </equals>
                  </with>
               </visibleWhen>
            </command>
      </menuContribution>
We need to set checkEnabled on the visibleWhen element to false, otherwise the expression will not be active. The with section uses activePartId as source. It is a string containing the ID of the active view/editor (see description).

The equals section compares the source value with org.eclipse.ui.navigator.ProjectExplorer, returning true when the Project Explorer View is active.

Run your Plug-in as a new Eclipse application and activate the Project Explorer to see the toolbar entry appear/disappear.

Test 2: Enable when Projects are selected


Next we want a button to be visible when at least one project is selected.
            <command
                  commandId="org.eclipse.ui.file.exit"
                  label="Test2"
                  style="push">
               <visibleWhen
                     checkEnabled="false">
                  <with
                        variable="selection">
                     <iterate
                           operator="or">
                        <adapt
                              type="org.eclipse.core.resources.IProject">
                        </adapt>
                     </iterate>
                  </with>
               </visibleWhen>
            </command>
Now we use the selection source provider, which provides the current selection. The iterate operator is nice as it tests all elements of a collection. By setting operator to or we define that at least one element of the collection has to pass the test.

There exists an instanceof operator which we could use to check against org.eclipse.core.resources.IProject. Unfortunately not all projects implement that interface. Instead they use the adapter framework. Therefore we use the adapt operator to see if the element can be adapted to an IProject. Of course this works for all classes implementing the interface directly too.


Test 3: Enable when exactly 2 txt Files are selected


Now for something more complex. A button shall be visible when exactly two resources are selected and both have an extension of .txt.
            <command
                  commandId="org.eclipse.ui.file.exit"
                  label="Test3"
                  style="push">
               <visibleWhen
                     checkEnabled="false">
                  <with
                        variable="selection">
                     <and>
                        <iterate
                              operator="and">
                           <test
                                 property="org.eclipse.core.resources.extension "
                                 value="txt">
                           </test>
                        </iterate>
                        <count
                              value="2">
                        </count>
                     </and>
                  </with>
               </visibleWhen>
            </command>
The property tester for file extensions is described in the documentation. The count operator counts the elements within a collection.


Test 4: Login/Logout

Let's add two more buttons for a very basic user login.
   <extension
         point="org.eclipse.ui.menus">
      <menuContribution
            allPopups="false"
            locationURI="menu:file?after=additions">
         <command
               commandId="com.codeandme.coreexpressions.commands.login"
               label="Login"
               style="push">
            <visibleWhen
                  checkEnabled="false">
               <with
                     variable="com.codeandme.coreexpressions.currentStatus">
                  <or>
                     <equals
                           value="startup">
                     </equals>
                     <equals
                           value="logged off">
                     </equals>
                  </or>
               </with>
            </visibleWhen>
         </command>
         <command
               commandId="com.codeandme.coreexpressions.commands.logout"
               label="Logoff"
               style="push">
            <visibleWhen
                  checkEnabled="false">
               <with
                     variable="com.codeandme.coreexpressions.currentUser">
                  <equals
                        value="John Doe">
                  </equals>
               </with>
            </visibleWhen>
         </command>
      </menuContribution>
We use dedicated commands that will set/reset the current user of our source provider from the previous post. The different source providers for both buttons do not necessarily make sense - we could achieve the same with just one source. Its just to give you an idea how it works.


We use our custom property tester to verify the current logon state and the current user.

Source Provider, Property Tester and the Expressions Framework

The expressions framework provides powerful means to declare expressions via XML and query them at runtime. Such expressions can be used without loading the defining Plug-in. You can find them in command/menu enablements. There you find visibleWhen, enabledWhen, activeWhen expressions. But you also can use them for your own needs.

In this example we will create our own Source Provider with a custom property tester (don't worry, I will explain this right ahead).

There is good documentation available for further reading.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online. 

Introduction

The expression framework can create/evaluate boolean expressions - something that in the end evaluates to true or false. To evaluate we need an object to work with (a source) and operators which return a boolean value.

Eclipse already defines commonly used sources and operators.

A simple example of an expression looks like this:
<visibleWhen>
     <and>
        <systemTest
              property="os.name"
              value="Linux">
        </systemTest>
        <systemTest
              property="os.arch"
              value="i386">
        </systemTest>
     </and>
<visibleWhen>

and is a basic operator that - of course - performs a boolean and operation. systemTest queries a java system property and compares it to a given value. So this expression evaluates to true only if we are working on an i386 linux machine.

Our source is not explicitely defined in the expression as the operator queries a static source System.getProperties().

Step 1: Creating a SourceProvider

For most expressions we will need sources that contain modifiable content. Use the predefined sources to get the active editor, the active selection or similar workbench related items. In this tutorial we will create a custom source to denote the current user.

Create a new Plug-in Project called com.codeandme.coreexpressions. In the plugin.xml create a new extension for org.eclipse.ui.services and add a new sourceProvider to it. Create two new variables for the sourceProvider:
set name to com.codeandme.coreexpressions.currentStatus and com.codeandme.coreexpressions.currentUser and leave priorityLevel set to workbench.

Now implement the sourceProvider by creating a class com.codeandme.coreexpressions.ExampleSourceProvider:
package com.codeandme.coreexpressions;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.ui.AbstractSourceProvider;
import org.eclipse.ui.ISources;

public class ExampleSourceProvider extends AbstractSourceProvider {

 public static final String CURRENT_STATUS = "com.codeandme.coreexpressions.currentStatus";
 public static final String CURRENT_USER = "com.codeandme.coreexpressions.currentUser";

 private Object fUser = null;
 private String fStatus = "startup";

 public ExampleSourceProvider() {
 }

 @Override
 public void dispose() {
 }

 @Override
 public Map getCurrentState() {
  HashMap<String, Object> map = new HashMap<String, Object>();
  map.put(CURRENT_USER, fUser);
  map.put(CURRENT_STATUS, fStatus);

  return map;
 }

 @Override
 public String[] getProvidedSourceNames() {
  return new String[] { CURRENT_USER, CURRENT_STATUS };
 }
}
We need to implement two methods:
  • getProvidedSourceNames()
    returns an array of source identifiers (variables). These are the same we already defined as variables in the plugin.xml.
  • getCurrentState()
    returns a Map<String, Object> with entries for each defined variable.

Step 2: Creating a property tester

Eclipse provides a generic expression operator test which allows to register custom tests.

Create a new extension for org.eclipse.core.expressions.propertyTesters with a unique id.

The type reflects to the kind of objects you'd get from the source provider. For now leave it to java.lang.Object.

The identifier of a specific property to check should be a fully qualified name. It consists of a namespace part and a properties name. It is good practice to set the namespace to your Plug-in identifier. Therefore set namespace to com.codeandme.coreexpressions and add two specific properties name,validated to properties. Hence our properties are named:
  • com.codeandme.coreexpressions.name
  • com.codeandme.coreexpressions.validated
Finally implement the class com.codeandme.coreexpressions.ExamplePropertyTester:
package com.codeandme.coreexpressions;

import org.eclipse.core.expressions.PropertyTester;

public class ExamplePropertyTester extends PropertyTester {

 private static final String NAME = "name";
 private static final String VALIDATED = "validated";

 public ExamplePropertyTester() {
 }

 @Override
 public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
  if (NAME.equals(property))
   return receiver.toString().equals(expectedValue);

  if (VALIDATED.equals(property)) {
   // do some background checks to see if user is validated
   return true;
  }

  return false;
 }
}
The receiver of the test() implementation is some object from a source provider. If you set your property tester to only accept objects of a certain type you need to add an instanceof check in the test() method.

Step 3: Your own expression

To use our source provider and property tester we can either create an expression for some activeWhen, enabledWhen or visibleWhen statement or we can extend org.eclipse.core.expressions.definitions to store the expression for later usage.

Create a new extension for org.eclipse.core.expressions.definitions with a unique id. Add a with section to define which source to use. Set variable to the name of our currentUser source: com.codeandme.coreexpressions.currentUser.

Add a test section to define our custom test. Set property to com.codeandme.coreexpressions.name and value to a string to compare against. In the sample I use "John Doe".

Make sure you activate forcePluginActivation. This defines that the Plug-in defining the property tester should be activated. If it is not activated, the property test cannot be performed.

The final definition:
         <with
               variable="com.codeandme.coreexpressions.currentUser">
            <test
                  forcePluginActivation="true"
                  property="com.codeandme.coreexpressions.name"
                  value="John Doe">
            </test>
         </with>
Step 4: Updating your source

Expressions do work right now, but our source is some kind of static. It won't change its status. Open ExampleSourceProvider and add following code:
 public void setCurrentUser(Object user) {
  fUser = user;
  fStatus = (user == null) ? "logged off" : "logged on";

  fireSourceChanged(ISources.WORKBENCH, CURRENT_USER, fUser);
  fireSourceChanged(ISources.WORKBENCH, CURRENT_STATUS, fStatus);
 }
fireSourceChanged() will tell Eclipse that a source changed and therefore forces expressions using this source to be re-evaluated.

To get an instance of your source provider you can query the ISourceProviderService:
ISourceProviderService service =(ISourceProviderService) PlatformUI.getWorkbench().getService(ISourceProviderService.class);
ISourceProvider provider = service.getSourceProvider(ExampleSourceProvider.CURRENT_USER);

Custom services

When implementing RCP applications you sometimes might need to define your own services. For example this could be an abstraction of a database or a core instance providing resources. Often this is done by implementing a singleton which is directly accessed by consumers. Such an implementation does not provide a separation between a service definition and the actual implementation.

With eclipse services we can achieve exactly that with very little overhead. This article is based on the online documentation on services.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Creating the service definition

Start with a new Plug-in Project called com.codeandme.customservice. On the Extensions tab of your plugin.xml add a new extension to org.eclipse.ui.services.

Step 2: Creating the service

We want to declare our service through an interface so we could easily exchange the implementation. Therefore create a new interface com.codeandme.customservice.ICustomService. Declaration of specific methods depends of what your service should provide and is not important for this example.

For the implementation create a new class com.codeandme.customservice.CustomServiceImpl with following content:
package com.codeandme.customservice;

public class CustomServiceImpl implements ICustomService {

 private static CustomServiceImpl fInstance = null;

 public static CustomServiceImpl getInstance() {
  if (fInstance == null)
   fInstance = new CustomServiceImpl();

  return fInstance;
 }

 private CustomServiceImpl() {
 }
}
Go back to your plugin.xml and set the serviceClass to com.codeandme.customservice.ICustomService.

Step 3: Creating a service factory

Create a factory class com.codeandme.customservice.CustomServiceFactory:
package com.codeandme.customservice;

import org.eclipse.ui.services.AbstractServiceFactory;
import org.eclipse.ui.services.IServiceLocator;

public class CustomServiceFactory extends AbstractServiceFactory {

 public CustomServiceFactory() {
 }

 @Override
 public Object create(@SuppressWarnings("rawtypes") Class serviceInterface, IServiceLocator parentLocator, IServiceLocator locator) {
  if (serviceInterface.equals(ICustomService.class)) 
   return CustomServiceImpl.getInstance();

  return null;
 }
}
Head back to your plugin.xml and set factoryClass to com.codeandme.customservice.CustomServiceFactory. Our final service declaration looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.ui.services">
      <serviceFactory
            factoryClass="com.codeandme.customservice.CustomServiceFactory">
         <service
               serviceClass="com.codeandme.customservice.ICustomService"></service>
      </serviceFactory>
   </extension>

</plugin>

Step 4: Using the service

When your application needs a service instance it can directly query the service locator with following snippet (from within a view):
ICustomService customService = (ICustomService) getSite().getService(ICustomService.class);
Or globally with:
ICustomService customService = (ICustomService) PlatformUI.getWorkbench().getService(ICustomService.class);
The source files for this article contain a separate Plug-in using the service.

Final thoughts

At first sight you don't gain a lot with this approach. Real world examples often would separate service definition and implementation into different Plug-ins. When the service is used through its interface and accessed through the ServiceLocator your clients do not have any dependency to the factory or the implementing class. This lets you easily exchange the implementation without altering the clients.

As found in the documentation a service factory might provide different services but for a dedicated serviceClass there may exist only one factory. Such a thing could be done by declaring your own extension points.

It might be a drawback that for services you need a dependency to UI Plug-ins.