Galileo: EMF-Databinding – Part 2


Introduce the new Properties API

In 3.5 Eclipse-Databinding introduces a new API which should be used in favor of the 3.4 EMFObservables-Factory because it provides many features who couldn’t be provided by the old API (e.g. the new API provides you with the possibility to observe nested attributes in Table/TreeViewers, allows you do observe different object types in the same viewer which is very handy in case of a TreeViewer).

Like in previous releases the Core-Eclipse-Databinding bundles define an API and provide an abstract base implementations and downstream modules need to provide the concrete implementation for the domain model implementation and its notification system.

Let’s take a look at the bundles and their dependencies:
Plugin Dependencies

As you notice EMF-Databinding is split into 2 bundles:

  • org.eclipse.emf.databinding: The core implementation of the Eclipse-Databinding-API which makes changes using EObject#eSet(), EObject#eAdd() and EObject#eRemove() methods
  • org.eclipse.emf.databinding.edit: Implementation which builds upon the EMF-Databinding-Core implementation but uses features provided by the EMF-Edit. Changes to the model instance are not made directly on the model-instance but are done by executing EMF-Commands on so called EditingDomain. Going through this additional framework provides us new features like Undo/Redo-Support. Naturally whenever you get new features you have to pay a price – going through this additional API causes a bit overhead.

An example might make the difference between modifying a domain instance in pure EMF vs EMF-Edit clearer. In pure EMF setting the lastname attribute of a Person is done like this:

public void example(Person p) {
  p.eSet(
    ProjectPackage.Literals.PERSON__LASTNAME,
    "Schindl"
  );
}

And when using EMF-Edit:

public void example(EditingDomain eDomain, Person p) {
  Command cmd = SetCommand.create(
    eDomain, 
    p, 
    ProjectPackage.Literals.PERSON__LASTNAME, 
    "Schindl");

  if( cmd.canExecute() ) {
    eDomain.getCommandStack().execute(cmd);
  }
}

Now that we saw the highlevel overview it’s time to take a closer look at the new Eclipse-Databinding-API. It is called Properties-API because of the name of the root interface org.eclipse.core.databinding.property.IProperty.

Interface and Class Overview

The nature of nested properties
A nested property is a property which is not the direct member of an observed class but one of its member instances. In our example application we have many different nested properties for example the lastname of a committer:
Nested Lastname Property
If we have a CommitterShip instance we are not able to directly access the last name but have to go through the person-property.

public void example(CommitterShip cship) {
  // CommitterShip.person
  Person p = cship.eGet(
    ProjectPackage.Literals.COMMITTER_SHIP__PERSON
  );

  // Person.lastname
  String lastname = p.eGet(
    ProjectPackage.Literals.PERSON__LASTNAME
  );
}

What we are doing here is traversing the object-tree to get access to the property we are interested in and this traversal is often referred to as following a path through the model.

Observing nested properties is quite a common use case but the 3.4 Databinding-API didn’t provided such support out of the box though it was available in some situations.

This changed with the new Properties-API which has built in support for nested properties and so the EMF-Databinding honors this with a set of factory methods (see below for more informations on this) and one special class describing the path to a nested property.
Feature Path

So specifing the path shown above we’d write Java code like this:

// /person/lastname
FeaturePath path = FeaturePath.fromList(
  ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
  ProjectPackage.Literals.PERSON__LASTNAME
);

The Factories
The EMF-Databinding implementations hide the implementation behind 2 factories:

EMFProperties
EMFProperties-Factory

This class provides factory methods to create instances of IEMFValueProperty, IEMFListProperty and IEMFMapProperty by passing the EStructuralFeature or the path to a nested EStructuralFeature the property should represent.

// Direct attribute
IEMFProperty pSurname = EMFProperties.value(
  ProjectPackage.Literals.PERSON__LASTNAME
);

// Nested attribute
IEMFProperty committerSurname = EMFProperties.value(
  FeaturePath.fromList(
    ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
    ProjectPackage.Literals.PERSON__LASTNAME
  )
);

EMFEditProperties

EMF-Edit Factory

This class provides factory methods to create instances of IEMFEditValueProperty, IEMFEditListProperty and IEMFEditMapProperty by passing the EStructuralFeature or the path to a nested EStructuralFeature the property should represent and the EditingDomain through which the modifications are made.

The usage of the API is very similar to the one of EMFProperties

// Direct attribute
IEMFEditProperty pSurname = EMFEditProperties.value(
  eDomain,
  ProjectPackage.Literals.PERSON__LASTNAME
);

// Nested attribute
IEMFEditProperty committerSurname = EMFEditProperties.value(
  eDomain,
  FeaturePath.fromList(
    ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
    ProjectPackage.Literals.PERSON__LASTNAME
  )
);

Observing single value attributes/properties
value_property

The IValueProperty implementations IEMFValueProperty and IEMFEditValueProperty provide the possibility to create observables of single value attribute/properties of an object like the lastname of Person.

emf_value_property

Let’s take a look how we can use this new API in our Java-Code. Code below only uses the Pure-EMF-API and not the EMF-Edit one because the only difference would be the creation of the IValueProperty (The EditingDomain has to be passed there).

// 1. Use case - Create an observable
public IObservableValue uc1(Person p) {
  IEMFValueProperty prop = EMFProperties.value(
    ProjectPackage.Literals.PERSON__LASTNAME
  );
  return prop.observe(p);
}

// 2. Use case - Create an observable for a nested attribute
public IObservableValue uc2(CommitterShip c) {
  IEMFValueProperty prop = EMFProperties.value(
    FeaturePath.fromList(
      ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
      ProjectPackage.Literals.PERSON__LASTNAME
    )
  );
  return prop.observe(c);
}

// 3. Use case - Create multiple observables
public IObservableValue[] uc3(Person p) {
  IEMFValueProperty[] props = EMFProperties.values(
    ProjectPackage.Literals.PERSON__LASTNAME,
    ProjectPackage.Literals.PERSON__FIRSTNAME,
  );
  return new IObservableValue[] { 
    props[0].observe(p), 
    props[1].observe(p) 
  };
}

// 4. Use case - Create multiple observables 
// for a nested attributes
public IObservableValue[] uc4(CommitterShip c) {
  IEMFValueProperty[] props = EMFProperties.values(
    FeaturePath.fromList(
      ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
      ProjectPackage.Literals.PERSON__LASTNAME
    ),
    FeaturePath.fromList(
      ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
      ProjectPackage.Literals.PERSON__FIRSTNAME
    )
  );
  return new IObservableValue[] { 
    props[0].observe(c), 
    props[1].observe(c) 
  };
}

// 5. Use case - Create a detail observable
public IObservableValue uc5(IObservableValue master) {
  IEMFValueProperty prop = EMFProperties.value(
    ProjectPackage.Literals.PERSON__LASTNAME
  );
  return prop.observeDetail(master);
}

// 6. Use case - Observe the lastname 
// attribute of list of persons
public IObservableMap uc6(IObservableSet persons) {
  IEMFValueProperty prop = EMFProperties.value(
    ProjectPackage.Literals.PERSON__LASTNAME
  );
  return prop.observeDetail(persons);
}

IEMFListProperty/IEMFEditListProperty
list_property

The IListProperty implementations IEMFListProperty and IEMFEditListProperty provide the possibility to create observables of multi valued attribute/properties like e.g. subprojects of a project.

emf_list_property
Let’s take a look at some lines of Java how we are using this API:

// 1. Use case - observe the subprojects
public IObservableList uc1(Project p) {
  IEMFListProperty prop = EMFProperties.list(
    ProjectPackage.Literals.PROJECT__SUBPROJECTS
  );
  return prop.observe(p);
}

// 2. Use case - observe the nested list
// The list of all subprojects of the projects parent
public IObservableList uc2(Project p) {
  IEMFListProperty prop = EMFProperties.list(
    FeaturePath.formList(
      ProjectPackage.Literals.PROJECT__PROJECT,
      ProjectPackage.Literals.PROJECT__SUBPROJECTS
    )
  );
  return prop.observe(p);
}

// 3. Use case - Observe a detail list
public IObservableList uc3(IObservableValue master) {
  IEMFListProperty prop = EMFProperties.list(
    ProjectPackage.Literals.PROJECT__SUBPROJECTS
  );
  return prop.observeDetail(master);
}

// 4. Use case - Combine to lists into one
public IObservableList uc4(Project p) {
  IEMFListProperty prop = EMFProperties.multiList(
      ProjectPackage.Literals.PROJECT__SUBPROJECTS,
      ProjectPackage.Literals.PROJECT__COMMITTERS
  );
  return prop.observe(p);
}

IEMFMapProperty/IEMFEditMapProperty
We are not useing maps in the example code so I’m not going to show the API in detail here but the idea behind it should be quite obvious. With the type of properties you can observe an java.util.Map-Type of multi-valued property.

NO IEMFSetProperty/IEMFEditSetProperty
You might ask why those to files are grayed out a bit in the screenshot. The reason is that support for this didn’t made it into EMF 2.5 but we are looking into it for 2.6.

Hidden secrets of the properties API
The IProperty API is quite an impressive piece of API – extremly powerful but still easy to use and in the next parts of this blog series we’ll see most the API in action. One of it’s big advantages is that it decouples the creation of the observable from specifying which attribute/property from which domain model one likes to observe which is really powerful when it comes to writing API code which should be domain model agnostic.

Suppose we write a library on top of Eclipse-Databinding which allows us to create text-input-forms in a more condensed way but the API should be useable with any domain model technology.

If we’d used the 3.4 Observables-Factories like (BeanObservables, EMFObservables, EMFEditObservables) we would have to write an specialized implementation for every widget technology because the specification of the attribute and how the binding is created is different for each of them.

Using the new Properties-API which defines a common description of a property a observable is going to be created later would lead to an API like this.

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.jface.databinding.swt.IWidgetValueProperty;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

import org.eclipse.emf.databinding.EMFUpdateValueStrategy;

/**
 * Helper class which builds a two column form 
 * with labels and text-fields
 * @param the value property type
 */
public class FormBuilder<P extends IValueProperty>
{
  private class Entry
  {
    private String label;
    private P property;
    private String nullMessage;

    private Entry(String label, 
      P property, 
      String nullMessage)
    {
      this.label = label;
      this.property = property;
      this.nullMessage = nullMessage;
    }
  }

  private List<Entry> entries = new ArrayList<Entry>();

  /**
   * Add a text entry
   * @param label the label to display
   * @param property the property to bind
   * @param nullMessage the message shown 
   *               when the property gets set to null
   */
  public void addTextEntry(String label, 
    P property, 
    String nullMessage)
  {
    entries.add(
      new Entry(label, property, nullMessage)
    );
  }

  /**
   * Build a two column form with the elements added
   *
   * @param dbc the databinding context
   * @param parent the parent the form is created on
   * @param object the object to bind
   * @return the form container
   */
  public Composite build(DataBindingContext dbc, 
    Composite parent, 
    Object object)  {
    Composite container = new Composite(parent, SWT.NONE);
    container.setLayout(new GridLayout(2, false));

    IWidgetValueProperty textProp = WidgetProperties.text(
      SWT.Modify
    );

    for (Entry e : entries)
    {

      Label l = new Label(container, SWT.NONE);
      l.setText(e.label);

      Text t = new Text(container, SWT.BORDER);
      t.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

      IObservableValue uiObs = textProp.observeDelayed(
        400, t
      );
      IObservableValue mObs;

      if (object instanceof IObservableValue)
      {
        mObs = e.property.observeDetail(
          (IObservableValue)object
        );
      }
      else
      {
        mObs = e.property.observe(object);
      }

      dbc.bindValue(
        uiObs, 
        mObs, 
        new EMFUpdateValueStrategy().setBeforeSetValidator(
          new EmptyStringValidator(e.nullMessage)
        ), 
        null
      );
    }

    return container;
  }
}

The UFaceKit-project has a similar abstraction to provide domain model agnostic API so something like this was doable also before the introduction of the new API. Still having this concept at the core of the implementation is highly appreciated.

Now that we got introduced into the most important parts of the new Properties-API we are ready to start working on our programm in the next blog series.

Advertisement
This entry was posted in EMF, Tutorials. Bookmark the permalink.

8 Responses to Galileo: EMF-Databinding – Part 2

  1. Pingback: Galileo: Improved EMF-Databinding-Support « Tomsondev Blog

  2. Thank you for doing this series. I’ve been interested in using the new Properties API with my EMF-Databinding project.

    Looking forward to the rest of the installments!

  3. SeB says:

    Hello, in one of you use case I wonder if there is a mistake.

    // 2. Use case – observe the nested list
    // The list of all subprojects of the projects parent
    public IObservableList uc2(Project p) {
    IEMFListProperty prop = EMFProperties.list(
    FeaturePath.formList(
    ProjectPackage.Literals.PROJECT__PROJECT,
    ProjectPackage.Literals.PROJECT__SUBPROJECTS
    )
    );
    return prop.observe(p);
    }
    you are talking of obsevring subproject or PARENT project so the feature path should be
    FeaturePath.formList(
    ProjectPackage.Literals.PROJECT__PARENT, ///and not PROJECT
    ProjectPackage.Literals.PROJECT__SUBPROJECTS
    )
    I think.
    Anyway this is great post.
    Thanks.

    SeB.

  4. Pingback: 2010 in review « Tomsondev Blog

  5. Jonathan says:

    Hi thanks for this topic 🙂 !

    I guess there’s a small mistake right here :

    // 2. Use case – Create an observable for a nested attribute
    public IObservableValue uc2(CommitterShip c) {
    IEMFValueProperty prop = EMFProperties.value(
    FeaturePath.fromList(
    ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
    ProjectPackage.Literals.PERSON__LASTNAME
    )
    );
    return prop.observe(p);
    }

    the return should be : return prop.observe(c); instead of return prop.observe(p);

    Thanks

  6. Alexandre Borgoltz says:

    Hi Tom, and thanks for your tuts – they’re always useful!

    A question about the nested properties : I like the “FeaturePath” concept very much, but it does not support multi valued features on the way (actually, it does accept them only at the last position).

    * I think that constraint shouldn’t be checked in the FeaturePath constructor but in the methods that use a featurePath and that do not support multi-valued features – that would allow me to reuse it as-is in my proprietary “multi-valued-friendly” implementation…
    * Is there any implementation (except my own!!) somewhere that does support multi-valued feature paths (by merging resulting values…)?

    Thanks in advance!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.