The Simple e4 IDE

This is just a short update on my Simple e4 IDE Demo. Last week I reimplemented the Outline View concept of the Eclipse IDE and ported over the JavaOutlinePage from JDT.

It currently lacks selection support to jump to places in the Editor but it looks very similar to what you get with Eclipse SDKs JDT (95% of the code is equal so that’s not really a suprise).

Installing Eclipse Translate View

I’ve just added an update-site to install Translation View into your Workbench. All you need to do is to add 2 update-sites:

After having successfully installed you should see a new category in your Eclipse Workbench:

And if opened it will show up like this:

e4 – Use e4-DI in 3.x

When talking about single sourceing in terms of e4 we talk about the following:

Write for e4 and run in e4-Applications, Eclipse 4.0 SDK and Eclipse 3.x SDK

In the first part of the series we are going to get familiar with how we can use the e4-Programming model which is build upon DI in a plain 3.x world. The application we’ll develop a small translator application/view.

The project sources are available from the EclipseLabs-Site I just created.

I’ll assume through out the tutorial that you’ve in depth knowledge about things like RCP, 3.x ServiceLocators, SWT/JFace and probably some Eclipse-Databinding background.

Step 1: Generate an 3.x ViewPart-Bundle

To start with the first thing we’ll do is to use the PDE-Project-Wizard “Plug-in with a view” to get an ViewPart which integrates into 3.x (e.g. named at.bestsolution.translate.view).

You should have an application looking like this:

Let’s now modify the content in the application to show a small translator UI which looks like this:

If you familiar with SWT (which i will assume through out this tutorial) here’s the code:

package at.bestsolution.translate.app;

import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

public class View extends ViewPart {
  public static final String ID = "at.bestsolution.translate.app.view";

  private Text term;

  /**
   * This is a callback that will allow us to create the viewer and initialize
   * it.
   */
  public void createPartControl(Composite parent) {
    GridLayout layout = new GridLayout(2, false);
    parent.setLayout(layout);

    Label l = new Label(parent, SWT.NONE);
    l.setLayoutData(
      new GridData(GridData.FILL, GridData.BEGINNING, true,false, 2, 1)
    );
    l.setText("Translator");

    l = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
    l.setLayoutData(
      new GridData(GridData.FILL, GridData.BEGINNING, true,false, 2, 1)
    );

    l = new Label(parent, SWT.NONE);
    l.setText("Term");

    term = new Text(parent, SWT.BORDER);
    term.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

    l = new Label(parent, SWT.NONE);
    l.setText("Translator");

    ComboViewer translator = new ComboViewer(parent);
    translator.getControl().setLayoutData(
      new GridData(GridData.FILL_HORIZONTAL)
    );

    l = new Label(parent, SWT.NONE);
    l.setText("Language");

    ComboViewer language = new ComboViewer(parent);
    language.getControl().setLayoutData(
      new GridData(GridData.FILL_HORIZONTAL)
    );

    Button b = new Button(parent, SWT.PUSH);
    b.setText("Translate");
    b.setLayoutData(
      new GridData(GridData.END, GridData.CENTER, false,false, 2, 1)
    );

    l = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
    l.setLayoutData(
      new GridData(GridData.FILL, GridData.CENTER, true,false, 2, 1)
    );

    l = new Label(parent, SWT.NONE);
    l.setText("Translation");
  }

  /**
   * Passing the focus request to the viewer's control.
   */
  public void setFocus() {
    term.setFocus();
  }
}

Step 2: Create the translation services

Create the Service-Interface-Bundle

Create a new plain OSGi-Bundle (without any dependencies in MANIFEST.MF) e.g. named “at.bestsolution.translate.services” and define an ITranslator-Interface like this:

package at.bestsolution.demo.translate.services;

import java.lang.reflect.InvocationTargetException;

public interface ITranslator {
  public class FromTo {
    public final String from;
    public final String to;

    public FromTo(String from, String to) {
      this.from = from;
      this.to = to;
    }
  }

  public String getName();
  public FromTo[] getFromTo();

  public String translate(FromTo fromTo, String term) throws InvocationTargetException;
}

Create Translator implementations

Lets’s as an example create a translator which uses the Google-Services for Translations by using this library from code.google.

The implementation class looks like this:

package at.bestsolution.translate.services.google;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import at.bestsolution.translate.services.ITranslator;

import com.google.api.translate.Language;
import com.google.api.translate.Translate;

public class GoogleTranslator implements ITranslator {
  private static FromTo[] FROM_TOS;
	
  static {
    ArrayList<FromTo> l = new ArrayList<FromTo>();
    for(Language fromLang : Language.values()) {
      for( Language toLanguage : Language.values() ) {
        if( fromLang != toLanguage ) {
          l.add(new FromTo(fromLang.name(), toLanguage.name()));
        }
      }
    }
    FROM_TOS =  l.toArray(new FromTo[0]);
  }

  public String getName() {
    return "Google Translate";
  }

  public FromTo[] getFromTo() {
    return FROM_TOS;
  }

  public String translate(FromTo fromTo, String term) throws InvocationTargetException {
    try {
      return Translate.execute(term, Language.valueOf(fromTo.from), Language.valueOf(fromTo.to));
    } catch (Exception e) {
      throw new InvocationTargetException(e);
    }
  }
}

And the registration through DS looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="at.bestsolution.translate.services.google.googletranslator">
   <implementation class="at.bestsolution.translate.services.google.GoogleTranslator"/>
   <service>
      <provide interface="at.bestsolution.translate.services.ITranslator"/>
   </service>
</scr:component>

Next we add a ITranslatorProviderService which collects the various ITranslator-Service implementation and makes them available as IObservableList an.

The service definition:

package at.bestsolution.translate.services;

import org.eclipse.core.databinding.observable.list.IObservableList;

public interface ITranslatorProvider {
	public IObservableList getTranslators();
}

It’s implementation:

package at.bestsolution.translate.services.internal;

import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;

import at.bestsolution.translate.services.ITranslator;
import at.bestsolution.translate.services.ITranslatorProvider;

public class TranslatorProviderComponent implements ITranslatorProvider {
	private IObservableList list = new WritableList(); 
	
	public IObservableList getTranslators() {
		return list;
	}

	public void addTranslator(final ITranslator translator) {
		list.getRealm().exec(new Runnable() {
			
			public void run() {
				list.add(translator);
			}
		});
	}
	
	public void removeTranslator(final ITranslator translator ) {
		list.getRealm().exec(new Runnable() {
			
			public void run() {
				list.remove(translator);
			}
		});
	}
}

It’s registration:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component 
  xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" 
  name="at.bestsolution.translate.services.translatorprovider">
  <implementation 
    class="at.bestsolution.translate.services.internal.TranslatorProviderComponent"/>
  <service>
    <provide interface="at.bestsolution.translate.services.ITranslatorProvider"/>
  </service>
  <reference 
    bind="addTranslator" 
    cardinality="0..n" 
    interface="at.bestsolution.translate.services.ITranslator" 
    name="ITranslator" 
    policy="dynamic" 
    unbind="removeTranslator"/>
</scr:component>

Consume the Service in the UI

Let’s for the start now consume the service directly throught the OSGi-Service registry. As a first step add a new method in our TranslatorView:

public class TranslatorView extends ViewPart {
	private IObservableList getTranslators() {
		Bundle bundle = FrameworkUtil.getBundle(getClass());
		BundleContext context = bundle.getBundleContext();
		ServiceReference reference = context.getServiceReference(ITranslatorProvider.class.getName());
		ITranslatorProvider pv = (ITranslatorProvider) context.getService(reference);
		
		return pv.getTranslators();
	}
}

We don’t have to deal with the case that the ITranslatorProvider is going away because our bundle depends on the TranslatorProvider-Services-Bundle hence if the service goes away we get shutdown as well.

Let’s use this IObservableList in our ui:

public class TranslatorView extends ViewPart {
  public void createPartControl(Composite parent) {
    // ....

    IObservableList translatorsObs = getTranslators();
		
    ComboViewer translator = new ComboViewer(parent);
    translator.getControl().setLayoutData(
      new GridData(GridData.FILL_HORIZONTAL));
    translator.setContentProvider(new ObservableListContentProvider());
    translator.setLabelProvider(new LabelProvider() {
      @Override
      public String getText(Object element) {
        return ((ITranslator)element).getName();
      }
    });
    translator.setInput(translatorsObs);

    l = new Label(parent, SWT.NONE);
    l.setText("Language");

    ComboViewer language = new ComboViewer(parent);
    language.getControl().setLayoutData(
      new GridData(GridData.FILL_HORIZONTAL)
    );
    language.setContentProvider(new ObservableListContentProvider());
    language.setLabelProvider(new LabelProvider() {
      @Override
      public String getText(Object element) {
        return ((FromTo)element).from + " - " + ((FromTo)element).to;
      }
    });

    IListProperty fromToProp = PojoProperties.list("fromTo");
    IValueProperty selectionProp = ViewerProperties.singleSelection();
		
    IObservableList input = fromToProp.observeDetail(selectionProp.observe(translator));
    language.setInput(input);

    // ....
  }
}

As you see this is already working very smooth but though the service stuff is working great we can use our View-Component only inside an RCP-Application (e.g. we can’t use it in a plain SWT one). At this point we can learn something from e4s way of doing things.

Learning from e4

Refactor code to use POJOs

In e4 everything you write are simply POJOs who annotated with DI-Annotation like:

  • @Inject
  • @PostConstruct

So what we do next is to factor out our UI-Component-Code is a simple POJO class which makes our ViewPart look like this:

package at.bestsolution.translate.view;

import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.*;

public class TranslatorView extends ViewPart {

  public static final String ID = "at.bestsolution.translate.view.views.TranslatorView";

  private TranslatorComponent component = new TranslatorComponent();

  public void createPartControl(Composite parent) {
    component.createUI(parent);
  }

	
  public void setFocus() {
    component.setFocus();
  }
}

We could stop at this point because our View-Component can now be used in any SWT-application but we can still improve the situation a bit by using e4 DI-Support which helps us to get access to OSGi-Services.

Next we have to set up Eclipse-DI ideally at the Workbench-Level so that e.g. in an RAP-Application every workbench instance has its own EclipseContext. So we are using the ui.services-Extension Point to set up such a context provider stuff:

The service-factory:

package at.bestsolution.translate.view;

import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.internal.services.IWorkbenchLocationService;
import org.eclipse.ui.services.AbstractServiceFactory;
import org.eclipse.ui.services.IServiceLocator;

@SuppressWarnings("restriction")
public class DIContextServiceFactory extends AbstractServiceFactory {

	@Override
	public Object create(@SuppressWarnings("rawtypes") Class serviceInterface, IServiceLocator parentLocator,
			IServiceLocator locator) {
		if( ! IEclipseContextProvider.class.equals(serviceInterface) ) {
			return null;
		}
		
		Object o = parentLocator.getService(serviceInterface);
		
		IWorkbenchLocationService wls = (IWorkbenchLocationService) locator.getService(IWorkbenchLocationService.class);
		final IWorkbenchWindow window = wls.getWorkbenchWindow();
		final IWorkbenchPartSite site = wls.getPartSite();

		if( window == null && site == null ) {
			return new IEclipseContextProviderImpl();
		} else if( o != null && site == null ) {
			return new IEclipseContextProviderImpl((IEclipseContextProvider) o);
		}
		
		return o;
	}

}

The workbench-service

package at.bestsolution.translate.view;

import org.eclipse.e4.core.contexts.IEclipseContext;

public interface IEclipseContextProvider {
	public IEclipseContext getContext();
}

The service implementation:

package at.bestsolution.translate.view;

import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;

public class IEclipseContextProviderImpl implements IEclipseContextProvider {
	private IEclipseContext context;
	
	public IEclipseContextProviderImpl(IEclipseContextProvider parent) {
		this.context = parent.getContext().createChild();
	}

	public IEclipseContextProviderImpl() {
		Bundle bundle = FrameworkUtil.getBundle(IEclipseContextProviderImpl.class);
		BundleContext bundleContext = bundle.getBundleContext();
		IEclipseContext serviceContext = EclipseContextFactory.getServiceContext(bundleContext);

		context = serviceContext.createChild("WorkbenchContext"); //$NON-NLS-1$
	}
	
	public IEclipseContext getContext() {
		return context;
	}
}

What’s left now is to refactor our POJO code a bit to use DI and make use of it in our ViewPart. Let’s at first adjust our POJO.

public class TranslatorComponent {
	private Text term;
	private ComboViewer translator;

	@Inject
	public TranslatorComponent(Composite parent) {
		createUI(parent);
	}

        // ...
}

We annotate our constructor with the @Inject annotation so that call the createUI-method there. Next we remove the OSGi-Service lookup with a method like this:

	@Inject
	void setTranslationProvider(@Optional ITranslatorProvider provider) {
		if( provider == null ) {
			if( ! translator.getControl().isDisposed() ) {
				translator.setInput(new WritableList());
			}
		} else {
			translator.setInput(provider.getTranslators());
		}
	}

This method gets called by the Injection Framework and passes in the currently available ITranslatorProvider-Instance in the OSGi-Service registry. Finally we adjust our ViewPart like this:

public class TranslatorView extends ViewPart {

	public static final String ID = "at.bestsolution.translate.view.views.TranslatorView";

	private IEclipseContext context;
	private TranslatorComponent component;

	public TranslatorView() {
		
	}
	
	public void createPartControl(Composite parent) {
		IEclipseContextProvider p = (IEclipseContextProvider) getSite().getService(IEclipseContextProvider.class);
		context = p.getContext().createChild();
		context.set(Composite.class, parent);
		component = ContextInjectionFactory.make(TranslatorComponent.class, context);
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	public void setFocus() {
		component.setFocus();
	}
	
	@Override
	public void dispose() {
		context.dispose();
		super.dispose();
	}
}

It’s time for a beer

Just a short note. Ralph Müller from the Eclipse Foundation is tomorrow 27th in Innsbruck (for the opensource day on 28th) and we’ll meet at 8pm for a beer or 2 at the Iris Pub next to the SOWI.

If you are in the Innsbruck area and are interested in joining us, feel free to do so – Ralph and me want to get in contact with people using Eclipse in Tyrol.

A week at e4 – A new milestone

Core and Compat-Layer

The work done last week was fixing the most important problems before signing of M6 this weekend, so not too many great new features have been developed but the ones already availabled got polished and improved.

The most important things are:

  • Editor-Activation time is reduced tremendously
  • Editor Sharing problems have been fixed
  • Sharing of Views like Package Explorer is available
  • A new perspective switcher has been added

An 4.0 SDK running on my OS-X looks like this:

Here are the URLs for the downloading the builds and looking at the N&N for M6

Take a look at 4.0 SDK FAQ to understand the relation between e4, 4.0 SDK and 3.x SDK.

ModelEditor

Some minor bug have been fixed but no great new feature has been added to not break existing functionality before the M6-Release.

Simple e4 IDE

This is a brand new category in my weekly status reports. I’ve started working on a small IDE like application which could be used to find missing wholes in our application and service model we need to address after the 4.0 release and help plug-in developers understand better how to prepare their code so that it runs on the e4-codebase without the need of a compat layer.

The main aspects of the Simple IDE Demo are:

  • Write some IDE like Application
  • Use git for version control
  • Experiment with Workbench-Services like a central ImageService, Internationalization-Service
  • Implement small examples people can look at and apply to their code
  • Make some of the Views workable in 3.x, 4.0 SDK and e4 runtimes


This screenshot shows the demos current state.

The code making up the above screenshot is using pure e4-APIs but naturally it doesn’t has the same functionality as the 3.x code still even those first experiments showed me that porting over stuff from e.g. JDT-UI is not as complex as I would have expected. I currently ported the Java-Syntax-Highlighting functionality and it took me about a day to get it working (though I’ve never ever worked with JFace-Text before), porting the eGit-Import Wizard was done in about 2 hours.

If you are interested in the progress, action items or even better work on one of the action items you can take a look at the wiki-page. The current export size with git-core and jdt-core bundles is around 20 MB.

A week at e4 – Share me please

In fact it’s been 2 weeks since my last blog entry about the progress that’s been made in e4. So what has happened in this 2 weeks:

Conferences

Kai Tödter and Boris Bokowski presented e4 at Jax2010 in Mainz. It is of real importance to tell the community about e4, which expectations they should have and more important which one they should NOT. The release we’ll ship this summer is an Early Adopter Release which though stable can’t be compared to the stability and feature richness of Eclipse Helios (and the 3.6 Release of the platform).

Core and Compat-Layer

A new annotation @Preference

One of the first nice things that’s been added is the support for Perference-Injection using a new @Preference-Annotation. So if you need access to a preference in your Part-Class you can now write:

import org.eclipse.e4.core.di.extensions.Preference;

public MyEditor {
  @Inject
  @Preference(value="textcolor")
  private String textcolor;
  
  @Inject
  public MyEditor(Composite composite) {
    
  }
}

Here’s a list of the most important annotation you might want to use:

  • @javax.inject.Inject: Mark a field/constructor/method to be recognized by the DI-Framework
  • @javax.inject.Named: Define the key under which the value to be injected in the DI-Context (by default the fully qualified class name is used)
  • @org.eclipse.e4.core.di.annotations.Optional: Mark an inject value to be optional so if it can’t be found no error is thrown
  • @org.eclipse.e4.core.di.annotations.Execute: Mark a method to be executed by the Command-Framework
  • @org.eclipse.e4.core.di.annotations.CanExecute: Mark a method to be visited by the Command-Framework to check if a command is enabled
  • @org.eclipse.e4.core.di.extensions.Preference: Mark a field/method-parameter to be filled with a value from the preference store
  • @org.eclipse.e4.workbench.ui.Persist: Mark a method in your Part-POJO to be called when the workbench issues a save-request

Shared Parts

One of the quite interesting things of Eclipse is the concept Perspectives and shared Parts between them. This is quite interesting from the point of the rendering framework because you suddenly don’t have a simple UI-Tree but one with Shared-Subtrees. I don’t go into details how this is solved but only let you know that this feature is now available in the latest builds.

Here are 2 screenshots of a running Eclipse 4.0 Early Adopter Release:
Java Perspective

CVS Perspective

I’ve been using the Eclipse 4.0 Early Adopters stream in the last few days a bit to work on e4-Samples and although it works, it’s sometimes a rough ride and I’d say productivity is 70% of the one I have with the 3.x stream but I’m still confident that at the time we ship it most of the problems are fixed (e.g. unconstrainted c&p in Dialogs).

Drag & Drop

Initial support for Drag and Drop has been added but it’s still a bit rough to use and often completely destroys your Workbench-Layout.

Model Tooling

New Features

Beside fixing bugs I added the following new features:

Support for Icon-URI creation

Initial Support for extended Application-Models

IMHO one of the coolest features of the Eclipse Platform 4.0 is that if the Application Model coming with it by default doesn’t suite your needs you are free to extend it and add support for features you need to have in your specific application.

An extended Application-Model can look like this:

To extend the e4-Application Model you use standard EMF-Tooling (in the screenshot we extend the main Application-Entity and add new feature). To make the Model-Editor aware of such new Entities and Features I introduced an extension point which allows external parties to extend the Base-Editor.

Take a look at the 2 editors where the upper one views an extended Application-Model whereas the lower one displays a default one.

Single-Sourceing

With single sourceing I mean that one writes a NATIVE e4-Component and runs it unmodified in:

Native e4.0-Application

Eclipse 4.0 SDK (=3.x codebase with compat-layer)

Eclipse 3.x SDK

I’ll describe in more detail in another blog post hopefully soon how I managed to make an e4-Component live other environments but for the ones who can’t wait.

The trick here is that the I’ve written an EditorPart-Implementation which makes the e4-Component believe it is running in an e4-Application (I simply set up such a virtual e4-Application-Context-Tree inside 3.x or inherit the compat-layer ones in 4.0).

This makes my Eclipse SDK 3.x / 4.0 integration look like this:

import org.eclipse.e4.tools.emf.editor3x.compat.E4CompatEditorPart;

public class E4WorkbenchModelEditor extends E4CompatEditorPart {
  public E4WorkbenchModelEditor() {
    super("platform:/plugin/org.eclipse.e4.tools.emf.ui/org.eclipse.e4.tools.emf.ui.internal.wbm.ApplicationModelEditor");
  }
}

So the Component URI which is in an e4-Application part of the application model is passed to a generic E4CompatEditorPart which holds all the magic and because the CSS-Themeing is applied to 3.x, 4.0 and native e4-applications (see here how and why this possible) my editor looks and behaves the same on all 3 platforms.