1. Accessing and extending the Eclipse context

1.1. Accessing the context

You can place objects directly in the IEclipseContext hierarchy to make them available to other model objects.

To access an existing context, you can use dependency injection if the relevant object is managed by the Eclipse runtime. This is the case for all classes the application model refers to, for example, parts and handlers. The following code demonstrates how to get access to the active IEclipseContext in which the handler is called.

package com.vogella.tasks.ui.handlers;

import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.Execute;

public class ShowMapHandler {
    @Execute
    public void execute(IEclipseContext context) {
        // add objects to the active local context injected into
        // this handler
        // ...
    }
}

If a model object implements MContext, you can use dependency injection to get the model object injected and call the getContext() method to access its context. For example, MPart, MWindow, MApplication and MPerspective extend MContext.

The following code demonstrates how to get the MApplication injected and how to access its IEclipseContext.

package com.vogella.tasks.ui.parts;

import jakarta.annotation.PostConstruct;

import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.swt.widgets.Composite;

public class TodoDetailsPart {

    @PostConstruct
    public void createControls(Composite parent, MApplication application) {
        IEclipseContext context = application.getContext();
        // add objects to or access objects from the application context
        // ...
    }
}

1.2. Objects and context variables

You can add key-value pairs directly to the IEclipseContext via its set() method. The following example creates a new context via the EclipseContextFactory.create() factory method call and adds some objects to it. Via the setParent() method call, the new context is connected to the context hierarchy.

@Inject
public void addingContext(IEclipseContext context) {

    // create a new IEclipseContext instance
    IEclipseContext myContext = EclipseContextFactory.create();

    // add objects to context
    myContext.set("mykey1", "Hello1");
    myContext.set("mykey2", "Hello2");

    // adding a parent relationship
    myContext.setParent(context);

    // alternatively you can create a new
    // context which has a parent/child
    // relationship via the
    // context.createChild() method call
}

Such a context can be used to instantiate an object via the Eclipse framework.

A context variable is a key that is declared as modifiable via the declareModifiable(key) method call.

@Inject
public void addingContext(IEclipseContext context) {
    // putting in some values
    context.set("mykey1", "Hello1");
    context.set("mykey2", "Hello2");

    // declares the named value as modifiable by descendants of this context

    // if the value does not exist in this context,
    // a null value is added for the name
    context.declareModifiable("mykey1");
}

Context variables are added to particular levels of the IEclipseContext hierarchy and can be modified using the modify() method rather than the set() method of the IEclipseContext. The modify() method searches up the chain to find the IEclipseContext defining the variable. If no entry is found in the context hierarchy, the value is set in the IEclipseContext in which the call started.

If the key already exists in the context, modify() requires that the key has been declared modifiable with the declareModifiable() method. Otherwise, the method throws an IllegalArgumentException.

You can add key-value pairs and context variables at different levels of the context hierarchy to supply different objects in your application.

1.3. Replacing existing objects in the IEclipseContext

Instead of adding new objects to the IEclipseContext hierarchy, you can override existing objects by using the same key. This allows you to change the behavior of your application.

For example, the WBWRenderer class puts an instance of the IWindowCloseHandler and the ISaveHandler interface into the local context of the MWindow model element. The IWindowCloseHandler object is responsible for the behavior when the window is closed. The default IWindowCloseHandler asks the user whether they want to save dirty parts (editors with changed content). You can change this default implementation by replacing the object in the context. The following example shows an @Execute method in a handler implementation that overrides this handler at runtime.

@Execute
public void execute(Shell shell, MWindow window) {
    IWindowCloseHandler handler = closingWindow -> MessageDialog.openConfirm(shell, "Close",
            "You will lose data. Really close?");
    window.getContext().set(IWindowCloseHandler.class, handler);
}
You could use this example in your lifecycle handler and subscribe to the UIEvents.UILifeCycle.APP_STARTUP_COMPLETE event. In the event handler, you would replace the IWindowCloseHandler in the context.

1.4. Accessing the IEclipseContext hierarchy from OSGi services

OSGi services are not directly part of the IEclipseContext hierarchy and are created by the OSGi runtime. The OSGi runtime does not support dependency injection based on the @Inject annotation.

The Eclipse framework registers the implementation of the MApplication interface as an OSGi service. This allows OSGi services to use the OSGi API to access the MApplication and its context via the getContext() method. As the EModelService is part of the MApplication context, you can search for other context elements via it.

1.5. Model add-ons

To participate in dependency injection with your custom Java objects, you can add them as model add-ons to the application model. The classes referred to by the model add-ons can access and modify the IEclipseContext or interact with other services, for example, the event system.

The following screenshot shows a custom model add-on registered in the application model.

Registering a model add-on

The following code shows an example implementation for the model add-on class. This add-on places an object into the IEclipseContext.

package com.vogella.tasks.ui.addons;

import jakarta.annotation.PostConstruct;

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

public class MyModelAddon {
    @PostConstruct
    public void init(IEclipseContext context) {
        // injected IEclipseContext comes from the application
        context.set("test1", "Hello");
    }
}

1.6. RunAndTrack

Via the runAndTrack() method, you can register a Java object of type RunAndTrack with an IEclipseContext. A RunAndTrack object is similar to a Runnable but has access to the context. It allows a client to keep some external state synchronized with one or more values in this context.

The framework calls the changed() method of the RunAndTrack immediately and again whenever one of the context values it accessed during its last run changes. It does not need to be explicitly unregistered. If its changed() method returns false, it is automatically unregistered from change tracking on this context.

The following example prints the label of the active part every time the user activates another part.

context.runAndTrack(new RunAndTrack() {
    @Override
    public boolean changed(IEclipseContext context) {
        if (context.get(IServiceConstants.ACTIVE_PART) instanceof MPart part) {
            System.out.println("Active part: " + part.getLabel());
        }
        return true; // keep tracking
    }
});

2. Extending the objects available for dependency injection

2.1. Creating and injecting custom objects

Using dependency injection for your custom objects has two flavors.

2.2. Using dependency injection to create objects

Dependency injection is not limited to the objects created by the Eclipse runtime. You can create an instance of a class that contains @Inject annotations based on a given IEclipseContext. For this, you use the ContextInjectionFactory class as demonstrated in the following code example.

// create instance of class
ContextInjectionFactory.make(MyJavaObject.class, context);

The ContextInjectionFactory.make() method creates the object. You can put it into the IEclipseContext hierarchy after the creation. If you place it into the IEclipseContext of the application, the created object is globally available.

You can either use an existing IEclipseContext or create a new one and connect it to the context hierarchy. A new context avoids key collisions and isolates your changes in a local context. Call the dispose() method on your local context if the object is no longer needed.

The following code demonstrates how to create a new IEclipseContext object and place values into it. This context can be used to create a new object.

IEclipseContext context = EclipseContextFactory.create();

// add your Java objects to the context
context.set(MyDataObject.class.getName(), data);
context.set(MoreStuff.class, moreData);

// dispose the context if you are done with it
context.dispose();

The next code example demonstrates how you can connect your new IEclipseContext object with an existing context hierarchy. The factory searches the hierarchy upwards to find the values requested by the instantiated class.

@Inject
public void createObjectInPart(IEclipseContext ctx) {
    // create a new local context
    IEclipseContext localCtx = EclipseContextFactory.create();
    localCtx.set(String.class, "Hello");

    // connect new local context with context hierarchy
    localCtx.setParent(ctx);

    // create object of type MyJavaObject via DI
    // uses the localCtx and searches upwards for required objects
    MyJavaObject o = ContextInjectionFactory.make(MyJavaObject.class, localCtx);

    // TODO do something with the "o" object
}

The ContextInjectionFactory.inject(Object, IEclipseContext) method allows you to perform injection on an existing object. For example, if you created the object with the new operator, you can still run dependency injection on it.

2.3. Create the custom objects automatically with @Creatable

If you want the Eclipse framework to create your custom objects for you, annotate them with @Creatable. This tells the Eclipse DI container to create a new instance of this class if it does not find an instance in the context. The automatically created instance is not stored in the context.

If you have a non-default constructor, you must use the @Inject annotation on the constructor to indicate that Eclipse should try to run dependency injection on it.

For example, assume that you have the following domain model.

package com.vogella.tasks.ui.creatable;

import org.eclipse.e4.core.di.annotations.Creatable;

@Creatable
public class Dependent {
    public Dependent() {
        // placeholder
    }
}
package com.vogella.tasks.ui.creatable;

import jakarta.inject.Inject;

import org.eclipse.e4.core.di.annotations.Creatable;

import com.vogella.tasks.model.TaskService;

@Creatable
public class YourObject {
    // constructor
    @Inject
    public YourObject(Dependent depend, TaskService service) {
        // placeholder
    }
}

As the Eclipse framework is allowed to create instances of the Dependent and the YourObject class, it creates them if an instance is requested via dependency injection. In this example, the arguments of the constructors can be satisfied. If no fitting constructor is found, the Eclipse framework throws an exception.

Assuming that you have defined the TaskService OSGi service in your application, you can get an instance of your YourObject class injected into a part, as demonstrated by the following code.

// add this for example to your playground part
@Inject
public void setYourObject(YourObject object) {
    System.out.println(object);
}

2.4. Automatically create objects in the application context with @Singleton

If the object should be created in the application context, use the @Singleton annotation in addition to the @Creatable annotation. This ensures that only one instance of the object is created in your application.

package com.vogella.tasks.ui.creatable;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import org.eclipse.e4.core.di.annotations.Creatable;

import com.vogella.tasks.model.TaskService;

@Creatable
@Singleton
public class YourObject {
    @Inject
    public YourObject(Dependent depend, TaskService service) {
        // placeholder
    }
}

3. Learn more and get support

This tutorial continues on Eclipse RCP online training or Eclipse IDE extensions with lots of video material, additional exercises and much more content.

4. Eclipse 4 resources

Home Tutorials Training Consulting Books Company Contact us


Get more...