Galileo: EMF-Databinding – Part 5

Though the main focus of this article is going to be the usage of the properties API to setup a TableViewer there are some importanted I didn’t explain in Part 4 of this series.

Using the properties API in master-detail (continued)

Showing validation states

The first interesting thing is how to present validation errors to user. I’m personally not a friend of ControlDecorations but prefer to display the validation error in the form-header.
Detail Area Error
This means the header presents an aggregation of all binding states and because this a quite common thing Eclipse-Databinding has built in support for this kind of thing.

private void addStatusSupport(ObservablesManager mgr, 
  final DataBindingContext ctx)
{
  AggregateValidationStatus aggregateStatus = 
    new AggregateValidationStatus(
      ctx.getValidationStatusProviders(),
      AggregateValidationStatus.MAX_SEVERITY
  );

  aggregateStatus.addValueChangeListener(
    new IValueChangeListener()
    {
      public void handleValueChange(ValueChangeEvent event)
      {
        handleStateChange(
          (IStatus)event.diff.getNewValue(), 
          ctx
        );
      }
    });
  mgr.addObservable(aggregateStatus);
}

The above aggregates the validation states of all validation status providers. By the default there’s a provider for binding states attached to a databinding context but one can attach also custom ones which opens an interesting opportunity e.g. when used inconjunction with the EMF-Validation framework (probably a topic for another Blog post in the next weeks).
The final task is to display the status to the user through the form header control like this

private void handleStateChange(IStatus currentStatus, 
  DataBindingContext ctx)
{
  if (form.isDisposed() || form.getHead().isDisposed())
  {
    return;
  }

  if (currentStatus != null 
    && currentStatus.getSeverity() != IStatus.OK)
  {
    int type = convertType(currentStatus.getSeverity());

    List<IMessage> list = new ArrayList<IMessage>();
    Iterator< ? > it = ctx.getValidationStatusProviders()
     .iterator()
    ;

    while (it.hasNext())
    {
      ValidationStatusProvider validationStatusProvider = 
        (ValidationStatusProvider)it.next()
      ;
      final IStatus status = (IStatus)
        validationStatusProvider.getValidationStatus()
        .getValue()
      ;

      if (!status.isOK())
      {
        list.add(new IMessage()
        {
          public Control getControl()
          {
            return null;
          }

          public Object getData()
          {
            return null;
          }

          public Object getKey()
          {
            return null;
          }

          public String getPrefix()
          {
            return null;
          }

          public String getMessage()
          {
            return status.getMessage();
          }

          public int getMessageType()
          {
            return convertType(status.getSeverity());
          }
        });
      }
    }
    form.setMessage("Data invalid", 
      type, 
      list.toArray(new IMessage [0])
    );
  }
  else
  {
    form.setMessage(null);
  }
}

Master-Detail, Validation and null
That’s kind of a strange title but and not easy to track down but an undiscovered regression in Eclipse-Databinding since about 2 years and was discovered too late to get fixed in 3.5 timeframe.

In short the problem is that if you are producing an validation error in for a model attribute which initially had the value null the wrong value on the target (=UI-Widget) is not cleared when modifing master. Believe this sounds more complex than it is reality and bug 278301 holds an nice example to reproduce the behaviour.

Nonetheless we need a working solution for now because of the location of the problem we won’t see a fix until 3.6 ships which is 1 year from now. I’m demonstrating in the following code a possible work-around but there are also other solutions. My solution uses the AggregateValidationStatus-support and forces an target update when the master observable is changed and the current validation status is NOT OK.

public class Util
{
  public static void masterDetailFixup(
    final DataBindingContext ctx, 
    IObservableValue master)
  {
    final AggregateValidationStatus s = 
      new AggregateValidationStatus(
        ctx, 
        AggregateValidationStatus.MAX_SEVERITY);

    master.addChangeListener(new IChangeListener()
      {

        public void handleChange(ChangeEvent event)
        {
          IStatus status = (IStatus)s.getValue();
          if (status != null && !status.isOK())
          {
            ctx.updateTargets();
          }
        }
      });
  }
}

Setting up a TableViewer with EMF-Databinding

Let’s now take a look a the next topic for this post where we are going to set up a TableViewer using Eclipse-Databinding for JFace/SWT and EMF. The TableViewer is taking up the complete lower right part of the application
Table Viewer

In part 3 we saw how to setup a TreeViewer, in part 4 we saw how we are setting up a TableViewer without columns and as you’ll see setting up a TableViewer with multiple columns is very similar. Let’s take a look at the code first

private void init(IViewSite site, 
  CTabFolder folder, 
  DataBindingContext ctx, 
  EditingDomain editingDomain, 
  IObservableValue master)
{
  final TableViewer viewer = new TableViewer(
    folder, SWT.FULL_SELECTION
  );

  viewer.getTable().setHeaderVisible(true);
  ObservableListContentProvider cp = 
    new ObservableListContentProvider()
  ;

  {
    IObservableMap[] attributeMap = new IObservableMap [2];
    attributeMap[0] = EMFEditProperties.value(
      editingDomain,
      FeaturePath.fromList(
        ProjectPackage.Literals.COMMITTER_SHIP__PERSON, 
        ProjectPackage.Literals.PERSON__LASTNAME
      )
    ).observeDetail(cp.getKnownElements());

    attributeMap[1] = EMFEditProperties.value(
      editingDomain,
      FeaturePath.fromList(
        ProjectPackage.Literals.COMMITTER_SHIP__PERSON, 
        ProjectPackage.Literals.PERSON__FIRSTNAME
      )
    ).observeDetail(cp.getKnownElements());

    TableViewerColumn column = new TableViewerColumn(
      viewer, SWT.NONE
    );
    column.getColumn().setText("Name");
    column.getColumn().setWidth(150);
    column.setLabelProvider(
      new GenericMapCellLabelProvider(
        "{0}, {1}", 
        attributeMap
      )
    );
  }

  {
    IObservableMap attributeMap = EMFEditProperties.value(
      editingDomain, 
      ProjectPackage.Literals.COMMITTER_SHIP__START
    ).observeDetail(cp.getKnownElements());

    TableViewerColumn column = new TableViewerColumn(
      viewer, SWT.NONE
    );
    column.getColumn().setText("Start");
    column.getColumn().setWidth(100);
    column.setLabelProvider(
      new GenericMapCellLabelProvider(
        "{0,date,short}", 
        attributeMap
      )
    );
  }

  {
    IObservableMap attributeMap = EMFEditProperties.value(
      editingDomain, 
      ProjectPackage.Literals.COMMITTER_SHIP__END
    ).observeDetail(cp.getKnownElements());

    TableViewerColumn column = new TableViewerColumn(
      viewer, SWT.NONE
    );
    column.getColumn().setText("End");
    column.getColumn().setWidth(100);
    column.setLabelProvider(
      new GenericMapCellLabelProvider(
        "{0,date,short}", 
        attributeMap
      )
    );
  }

  IListProperty prop = EMFEditProperties.list(
    editingDomain, 
    ProjectPackage.Literals.PROJECT__COMMITTERS
  );
  viewer.setContentProvider(cp);
  viewer.setInput(prop.observeDetail(master));

  MenuManager mgr = new MenuManager();
  mgr.add(
    new Action(
      "Hide historic committers", 
      IAction.AS_CHECK_BOX
    )
  {
    @Override
    public void run()
    {
      if (isChecked())
      {
        viewer.addFilter(new ViewerFilterImpl());
      }
      else
      {
        viewer.setFilters(new ViewerFilter [0]);
      }
    }
  });

  viewer.getControl().setMenu(
    mgr.createContextMenu(viewer.getControl())
  );
  site.registerContextMenu(
    Activator.PLUGIN_ID + ".committers", mgr, viewer);
}

The only thing not already explained before is a special type of CellLabelProvider which allows one to use MessageFormat-Syntax to specify how the label is constructed. This label provider is not part of the JFace-Databinding implementation but written by me but it’s quite easy to implement.

public class GenericMapCellLabelProvider 
  extends ObservableMapCellLabelProvider
{
  private IObservableMap[] attributeMaps;
  private String messagePattern;

  /**
   * Create a new label provider
   * @param messagePattern the message pattern
   * @param attributeMaps the values to observe
   */
  public GenericMapCellLabelProvider(
    String messagePattern, 
    IObservableMap... attributeMaps)
  {
    super(attributeMaps);
    this.messagePattern = messagePattern;
    this.attributeMaps = attributeMaps;
  }

  @Override
  public void update(ViewerCell cell)
  {
    Object element = cell.getElement();
    Object[] values = new Object [attributeMaps.length];
    int i = 0;
    for (IObservableMap m : attributeMaps)
    {
      values[i++] = m.get(element);
      if (values[i - 1] == null)
      {
        cell.setText("");
        return;
      }
    }
    cell.setText(
      MessageFormat.format(messagePattern, values)
    );
  }
}

That’s all we need to have a working TableViewer though there’s even more one can do with the Databinding-API e.g. Inline-Editing is supported and there are helpers to setup viewers in even less lines of code.

Galileo: EMF-Databinding – Part 4

Using the properties API in master-detail

In the last blog entry we saw how to set up a the left part of our example application which is a JFace-TreeViewer. This blog article deals with the upper right part of the application detail_area
who allows the user to edit the data of the selected project in the TreeViewer which is a very common use case in Databinding and called Master-Detail-Scenario where the TreeViewer acts as the master and the upper right area as the detail part.

Creation of the master observable

Looking at the API for Master-Detail
Master Detail API
we recognize that we need to have a Master-ObservableValue when creating the Detail-ObservableValue so before we can proceed we need to go back to the ProjectExplorerPart who is responsible for the TreeViewer creation and make it provide us an ObservableValue.
Most of the time the creation of the master observable is quite easy because the only thing one needs to do is to observe the current selection like this:

private IObservableValue createMasterObs(Viewer viewer) {
 return ViewerProperties.singleSelection().observe(viewer);
}

but in our case it’s a bit more difficult because our tree has different object types (Project and CommitterShip) but our detail can only handle Projects which means we can’t use the selection directly but have to add a mediator in between.

The mediator we are useing is a WritableValue which we declare in the class body:

/**
 * Part responsible for rendering the project tree
 */
public class ProjectExplorerPart
{
  // ...
  private IObservableValue master = new WritableValue();
  // ...
}

afterwards we observe the TreeViewer-Selection and adjust the WritableValue manually:

private TreeViewer init(Composite parent, 
  Foundation foundation)
{
  TreeViewer viewer = new TreeViewer(parent);

  // ...

  IObservableValue treeObs = 
    ViewerProperties.singleSelection().observe(viewer);
  treeObs.addValueChangeListener(new IValueChangeListener() 
  {
    public void handleValueChange(ValueChangeEvent event) {
      if (event.diff.getNewValue() instanceof Project) {
        Project pr = (Project)event.diff.getNewValue();
        master.setValue(pr);
      } else if (event.diff.getNewValue() != null) {
        CommitterShip cm = 
          (CommitterShip)event.diff.getNewValue();
        master.setValue(cm.getProject());
      }     
    }
  });

  // ...
}

Create the detail part

Binding with default conversion

We can now go on at the upper right side and use the obervable to bind our detail controls. All the code shown below resides in ProjectFormAreaPart. For standard text controls where no conversion is needed this is straight forward:

private void createFormArea(
  IViewSite site,
  final Composite parent,
  FormToolkit toolkit,
  final IModelResource resource,
  ObservablesManager manager,
  final IObservableValue master)
{
  final EditingDomain editingDomain = 
    resource.getEditingDomain();
  ctx = new EMFDataBindingContext();

  // ...

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

  final IEMFValueProperty shortProp = 
    EMFEditProperties.value(
      editingDomain, 
      ProjectPackage.Literals.PROJECT__SHORTNAME
   );

  toolkit.createLabel(body, "&Short name");
  Text t = toolkit.createText(body, "");
  t.setLayoutData(
    new GridData(SWT.FILL, SWT.DEFAULT, true, false, 2, 1)
  );
  ctx.bindValue(
    prop.observeDelayed(400, t), 
    shortProp.observeDetail(master)
  );
  // ...
}

Though as mentioned above it is straight forward there are still some things to notice:

  • EMFDatabindingContext: Is needed because IEMFObservables are exposing the EStructuralFeature as their value type but the default converters (e.g. String to Integer, Integer to String) who are created by the standard DatabindingContext work on top of java.lang.Class informations. EMFDatabindingContext uses default EMF conversion strageties and applies them instead.
  • IWidgetValueProperty.observeDelayed(Widget,int): This is an important detail to improve the users Undo/Redo experience because if not used every key stroke would result in an attribute change which would result in an undo/redo command. IWidgetValueProperty.observeDelayed() instructs the created observable to wait for 400 milliseconds before informing syncing the value back to the model and if another change happens within this time to cancel the update process and wait for another 400 milliseconds resulting in far less model changes and an improved undo/redo experience.

Binding with customized conversion

Now that we learned how to bind a Text-Widget to a String-property let’s take a look at another slightly more interesting binding use case – binding a Text-Widget to a Date-property which involves data conversion from Date to String and String to Date. EMFDatabindingContext comes with a standard implementation for this but our users might not expect to see a DateTime-Value including timezone informations when editing a date. So the first thing we need to do is create an improved Date to String and String to Date converter:

/**
 * Convert a String to a date
 */
public class StringToDateConverter extends Converter
{
  private List<DateFormat> formats = 
    new ArrayList<DateFormat>();

  private String message;

  /**
   * New converter
   * @param message message when conversion fails
   */
  public StringToDateConverter(String message)
  {
    super(String.class, Date.class);
    this.message = message;
    formats.add(
      DateFormat.getDateInstance(DateFormat.SHORT)
    );
    formats.add(new SimpleDateFormat("yyyy-MM-dd"));
  }

  public Object convert(Object fromObject)
  {
    if (fromObject != null 
      && fromObject.toString().trim().length() == 0)
    {
      return null;
    }

    for (DateFormat f : formats)
    {
      try
      {
        return f.parse(fromObject.toString());
      }
      catch (ParseException e)
      {
        // Ignore
      }
    }

    throw new RuntimeException(message);
  }
}

To implement such a converter one normally inherits from a given base class named Converter and implements the convert-method. In this case the value conversion is done useing Java’s DateFormat-classes. As a special addon an empty String is converted into a null value. This converter is used as at the target to model side converting the value entered into the UI into the model type. The converter for the other side looks like this:

/**
 * Convert a date to a string
 */
public class DateToStringConverter extends Converter
{
  private DateFormat format = 
    DateFormat.getDateInstance(DateFormat.SHORT);

  /**
   * New converter
   */
  public DateToStringConverter()
  {
    super(Date.class, String.class);
  }

  public Object convert(Object fromObject)
  {
    if (fromObject == null)
    {
      return "";
    }
    return format.format(fromObject);
  }
}

To teach Eclipse-Databinding to use our custom converters instead of the default ones we simply need to set them on the UpdateValueStrategys passed to DatabindingContext.bindValue(IObservableValue,IObservableValue,UpdateValueStrategy,UpdateValueStrategy). Setting such a custom conversion strategy though leads to many lines of code you repeat in many places. What I do in my project to reduce this amout of code is to move the creation of a common UpdateValueStrategy like String-Date-String into a factory like this:

public class UpdateStrategyFactory
{
  public static EMFUpdateValueStrategy stringToDate(
    String message)
  {
    EMFUpdateValueStrategy strat = 
      new EMFUpdateValueStrategy();

    StringToDateConverter c = 
      new StringToDateConverter(message);

    strat.setConverter(c);
    return strat;
  }

  /**
   * Create an update strategy which converts a date 
   * to a string
   * @return the update strategy
   */
  public static EMFUpdateValueStrategy dateToString()
  {
    EMFUpdateValueStrategy strat = 
      new EMFUpdateValueStrategy();
    DateToStringConverter c = new DateToStringConverter();
    strat.setConverter(c);
    return strat;
  }
}

resulting into binding code like this:

IEMFValueProperty mProp = EMFEditProperties.value(
  editingDomain, 
  ProjectPackage.Literals.PROJECT__END
);

toolkit.createLabel(body, "End Date");
Text t = toolkit.createText(body, "");
t.setLayoutData(
  new GridData(SWT.FILL, SWT.DEFAULT, true, false, 2, 1)
);
ctx.bindValue(
  prop.observeDelayed(400, t),
  mProp.observeDetail(master),
  UpdateStrategyFactory.stringToDate(
    NLSMessages.ProjectAdminViewPart_EndDateNotParseable
  ),
  UpdateStrategyFactory.dateToString());

Binding with customized conversion and validation

But there’s even more you can do with the UpdateValueStrategy – You ensure for example that an a project is not allowed to have an empty == null start date by setting an IValidator on the target to model UpdateValueStrategy. Once more this method is part of the UpdateStrategyFactory introduced above.

/**
  * Create an update strategy which converts a string to 
  * a date and ensures that the date value on the 
  * destinations  is not set to null
  * @param convertMessage the message shown when 
  *   the conversion fails
  * @param validationMessage the message when the 
  *   value is set to null
  * @return the update strategy
  */
public static EMFUpdateValueStrategy stringToDateNotNull(
  String convertMessage, final String validationMessage)
{
  EMFUpdateValueStrategy strat = 
    stringToDate(convertMessage);

  strat.setBeforeSetValidator(new IValidator()
  {
    public IStatus validate(Object value)
    {
      if (value == null)
      {
        return new Status(
          IStatus.ERROR, 
          Activator.PLUGIN_ID, 
          validationMessage
        );
      }
      return Status.OK_STATUS;
    }
  });
  return strat;
}

and is used like this

IEMFValueProperty mProp = EMFEditProperties.value(
  editingDomain, 
  ProjectPackage.Literals.PROJECT__START
);
toolkit.createLabel(body, "Start Date");
Text t = toolkit.createText(body, "");
t.setLayoutData(
  new GridData(SWT.FILL, SWT.DEFAULT, true, false, 2, 1)
);
ctx.bindValue(
  prop.observeDelayed(400, t), 
  mProp.observeDetail(master), 
  // Custom conversion
  UpdateStrategyFactory.stringToDateNotNull(
    NLSMessages.ProjectAdminViewPart_StartDateNotParseable,
    "Start date must not be null"
  ), 
  UpdateStrategyFactory.dateToString()
);

ensuring that the start date is not set to null.

Binding to a list of values

The last binding presented is how to bind the value of a multi value attribute to to a Table-Widget which used as a List in this case.

IEMFListProperty mProp = EMFEditProperties.list(
  editingDomain, 
  ProjectPackage.Literals.PROJECT__PROJECTLEADS);

toolkit.createLabel(body, "Project Leads").setLayoutData(
  new GridData(SWT.TOP, SWT.DEFAULT, false, false)
);

Table c = toolkit.createTable(
  body, 
  SWT.MULTI | SWT.FULL_SELECTION 
  | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER
);

final TableViewer tv = new TableViewer(c);
tv.setLabelProvider(new ColumnLabelProvider()
{
  @Override
  public String getText(Object element)
  {
    Person p = (Person)element;
    return p.getLastname() + ", " + p.getFirstname();
  }
});
tv.setContentProvider(new ObservableListContentProvider());
tv.setInput(mProp.observeDetail(master));

The code shown here is not much different from the one showing how to setup a TreeViewer though much simpler for a none hierarchical widget. We are also using a default LabelProvider here instead of one who observes the attributes shown. Now let’s take a look at the dialog which gets opened when the Add … button is pressed.
Filter Dialog
The Eclipse-RCP-Framework provides a Dialog implementation named FilteredItemsSelectionDialog we can subclass and customize it

/**
 * Dialog to find persons
 */
public class PersonFilterDialog extends 
  FilteredItemsSelectionDialog
{
  private final IModelResource resource;

  /**
   * Create a new dialog
   * @param shell the parent shell
   * @param resource the model resource
   */
  public PersonFilterDialog(Shell shell, 
    IModelResource resource)
  {
    super(shell);
    this.resource = resource;

    setListLabelProvider(new LabelProvider()
      {
        @Override
        public String getText(Object element)
        {
          if (element == null)
          {
            return "";
          }
          return PersonFilterDialog.this.getText(
            (Person)element
          );
        }
      });

    setDetailsLabelProvider(new LabelProvider()
      {
        @Override
        public String getText(Object element)
        {
          if (element == null)
          {
            return "";
          }
          return PersonFilterDialog.this.getText(
            (Person)element
          );
        }
      });
  }

  private String getText(Person p)
  {
    return p.getLastname() + " " + p.getFirstname();
  }

  @Override
  protected IStatus validateItem(Object item)
  {
    return Status.OK_STATUS;
  }

  @Override
  protected Comparator< ? > getItemsComparator()
  {
    return new Comparator<Person>()
      {

        public int compare(Person o1, Person o2)
        {
          return getText(o1).compareTo(getText(o2));
        }
      };
  }

  @Override
  public String getElementName(Object item)
  {
    Person p = (Person)item;
    return getText(p);
  }

  @Override
  protected IDialogSettings getDialogSettings()
  {
    IDialogSettings settings = Activator.getDefault()
      .getDialogSettings().getSection("committerdialog");

    if (settings == null)
    {
      settings = Activator.getDefault()
        .getDialogSettings()
        .addNewSection("committerdialog");
    }
    return settings;
  }

  @Override
  protected void fillContentProvider(
    AbstractContentProvider contentProvider, 
    ItemsFilter itemsFilter, 
    IProgressMonitor progressMonitor) throws CoreException
  {
    for (Person p : resource.getFoundation().getPersons())
    {
      if (progressMonitor.isCanceled())
      {
        return;
      }

      contentProvider.add(p, itemsFilter);
    }
  }

  @Override
  protected ItemsFilter createFilter()
  {
    return new ItemsFilter()
      {

        @Override
        public boolean isConsistentItem(Object item)
        {
          return true;
        }

        @Override
        public boolean matchItem(Object item)
        {
          Person p = (Person)item;
          return matches(p.getLastname() + " " 
            + p.getFirstname()
          );
        }

      };
  }

  @Override
  protected Control createExtendedContentArea(
    Composite parent)
  {
    return null;
  }
}

and use it in our code like this

Button b = toolkit.createButton(
  buttonContainer, 
  "Add ...", 
  SWT.FLAT);

gd = new GridData(SWT.DEFAULT, SWT.DEFAULT);
gd.horizontalAlignment = SWT.FILL;
b.setLayoutData(gd);
b.addSelectionListener(new SelectionAdapter()
{
  @Override
  public void widgetSelected(SelectionEvent e)
  {
    PersonFilterDialog dialog = new PersonFilterDialog(
      parent.getShell(), 
      resource
    );

    if (dialog.open() == IDialogConstants.OK_ID)
    {
      Command cmd = AddCommand.create(
        editingDomain,
        master.getValue(),
        ProjectPackage.Literals.PROJECT__PROJECTLEADS,
        dialog.getFirstResult());
      if (cmd.canExecute())
      {
        resource.executeCmd(cmd);
      }
    }
  }
});

That’s it for today in the next part we’ll take a look at setting up of TableViewer with different columns and how the nice validation stuff is working.

Galileo: EMF-Databinding – Part 3

Setting up a TreeViewer with EMF-Databinding

The RCP-Application
With this article we are starting to write our RCP-Application which uses Eclipse-Databinding. I’m not going into detail how you create an RCP-Application (I simply use the PDE-Wizard) but assume that you are familiar with this kind of thing.

Still I’d like to describe the application design and the reason behind decisions I made a bit. The first and most important thing is that I’m not a fan of EditorPart and I think for none-file resources the IEditorInput stuff simply doesn’t make a lot of sense so all applications I’ve written since I develop RCP are ViewParts.

A misconception I often see when I say that my RCP-Applications are free from editors is that people ask: “How are you going to be part of Workbench-Dirty/Save-Lifecyle if you use a ViewPart?”.

The answer is quite simple the only thing you have to do is to make your ViewPart implement ISaveablePart2 and then your view is part of the workbench-dirty state lifecycle.

Application Parts

The screen shot from above shows that how the applications UI code is broken up in smaller logical parts:

  • ProjectAdminViewPart:
    This is the main UI area which is subclassing ViewPart and registered into the workbench usin the views-Extension-Point.
  • ProjectExplorerPart:
    This class makes up the left part of the application showing a TreeViewer to select project, create subprojects, … . The internals of this part are described in more detail in this blog post later on.
  • ProjectFormAreaPart:
    This class makes up the right upper part showing a set of input controls who act as a detail part of the ProjectExplorerPart. The internals of this part are explained in detail in Part 4 of this blog series.
  • ProjectCommittersPart:
    This class makes up the right lower part showing a TableViewer. The internals of this part are explained in detail in Part 5 of this blog series.

Before we dive into the main topic of this blog post I’d like to show some interesting code found inside the ProjectAdminViewPart because it shows some interesting RCP features and introduces a small but often forgotten databinding stuff leading to memory leaks in conjunction with databinding.

Restoring view state information
The first interesting thing is that the content of the main area is created using a SashForm to give the user the possibility to define how much space the left part should occupy. The user naturally expects that when he starts the application the next time that the UI comes up in the same state it’s been when shutting down so we need to store the sash-weights when shutting down and restore them on the next start up.

The Eclipse-RCP-Framework provides a possibility to make this happen by using an IMemento to persist informations across application launches the only thing you to do is to use the API:

public class ProjectAdminViewPart 
  extends ViewPart implements ISaveablePart2
{
  private float divider = 0.2f;
  private SashForm sashForm;

  @Override
  public void init(final IViewSite site, IMemento memento) 
    throws PartInitException
  {
    super.init(site, memento);
    if (memento != null 
      && memento.getFloat(DIVIDER_KEY) != null)
    {
      divider = memento.getFloat(DIVIDER_KEY);
    }

    listener = new PartListenerImpl(site);
    site.getPage().addPartListener(listener);
  }

  @Override
  public void saveState(IMemento memento)
  {
    super.saveState(memento);
    int total = sashForm.getWeights()[0] 
                       + sashForm.getWeights()[1];
    memento.putFloat(
      DIVIDER_KEY, 
      sashForm.getWeights()[0] * 1.f / total);
  }

  @Override
  public void createPartControl(Composite parent)
  {
    // ...
    sashForm = new SashForm(parent, SWT.HORIZONTAL);
    // ...
    int left = (int)(100 * divider);
    sashForm.setWeights(new int []{ left, 100 - left });
  }
}

Avoid listener leaking
There’s something I very often see in databinding code from various people (even those who have a lot of databinding knowledge) is that they are not diposing their observables once not more needed which leads to memory leaks. This means that we have to remember all observables created and dispose them manually when not needed any more.

An exception to this are observables created for SWT-Widgets because they dispose themselves when the SWT-Widget is disposed but for all others the rule is the one who created it has to dispose it. Databinding provides a class named ObservablesManager which helps you with this.

In 3.5 EMF-Databinding added new API to ObservableManager which would make collecting observables very easy but apparently a bug was found very late in the release cycle and so the API is not useable currently. The API in question is used like this:

ObservablesManager mgr = new ObservablesManager();
mgr.runAndCollect(new Runnable()
  {
    public void run()
    {
      // your code which creates observables
    }
  });

Though the default API is broken for EMF-Databinding we can add an easy fix so that the API at least collects all IEMFObservable so that we only have to deal with things like ComputedValue and such stuff because as stated above SWT-Observables dispose themselves when the SWT-Widget is disposed.

So our code looks like this:

  @Override
  public void createPartControl(Composite parent)
  {
    // ...
    /* 
     * Track the creation of observables so that we 
     * don't leak listeners when the view part is closed
     */
    mgr = new EMFObservablesManager();
    defaultMgr = new ObservablesManager();
    mgr.runAndCollect(new Runnable()
      {

        public void run()
        {
          projectExplorer = new ProjectExplorerPart(
            getViewSite(), 
            sashForm, 
            toolkit, 
            resource.getFoundation(), 
            defaultMgr);

          projectDataForm = new ProjectFormAreaPart(
            getViewSite(),
            sashForm,
            toolkit,
            resource,
            defaultMgr,
            projectExplorer.getProjectObservable());
        }
      });
    // ...
  }

Setting up a TreeViewer
Enough about the generic stuff and let’s now concentrate on the main topic of this blog.
Project Explorer
Though I guess most of you know how to set up a standard TreeViewer here a short description:

  1. Set a CellLabelProvider: Responsible to translate the model object into a visual representation (text,colors,image,…). The CellLabelProvider-API was introduced in 3.3 and on top it JFace provides many cool new features like StyledText-Support (different fonts and colors in one cell) and ToolTip-Support. It’s worth revisiting your old code and replace LabelProvider through CellLabelProvider subclasses
  2. Set an ITreeContentProvider: Responsible to translate the input into a tree-structure.
  3. Set an input: Any input the ITreeContentProvider can handle

Createing the content provider
The Eclipse-RCP-Platform comes with a plugin named org.eclipse.jface.databinding which provides databinding support and implementation classes for SWT and JFace controls. One of those classes is ObservableListTreeContentProvider which is a class implementing the ITreeContentProvider and makes setting up an observed tree quite easily.

The ObservableListTreeContentProvider can’t do all the work on its own but expects us to provide it helper classes:

  • TreeFactory: responsible to create IObservableLists to observe the childitems of a treenode
  • TreeStructureAdvisor: responsible to provide informations on how to access the model parent of an item and helping to make the decision if an item has child-items

Looking at the picture above shows us that our structure is a bit of an virtual one when we compare it to the domain instance where we would only see the project-subprojects relation on the first sight.
Project Class
Our viewer though displays to different multi-value features as children in the Tree:

  • subprojects whose elements are of type Project
  • committerships whose elements are of type CommitterShip

Displaying different Object-Types was not supported in 3.4 and is a new feature in the new Databinding implementation as well as the possibility to combine 2 multi-value features into a single observable list which is for me personally one of the coolest features in the Properties-API.

  private static class TreeFactoryImpl 
    implements IObservableFactory
  {
    private IEMFListProperty multi = EMFProperties.multiList(
      ProjectPackage.Literals.PROJECT__SUBPROJECTS,
      ProjectPackage.Literals.PROJECT__COMMITTERS);

    public IObservable createObservable(final Object target)
    {
      if (target instanceof IObservableList)
      {
        return (IObservable)target;
      }
      else if (target instanceof Project)
      {
        return multi.observe(target);
      }

      return null;
    }
  }

  private static class TreeStructureAdvisorImpl 
    extends TreeStructureAdvisor
  {
    @Override
    public Object getParent(Object element)
    {
      if (element instanceof Project)
      {
        return ((Project)element).getParent();
      }

      return null;
    }

    @Override
    public Boolean hasChildren(Object element)
    {
      if (element instanceof Project 
        && (
          ((Project)element).getCommitters().size() > 0 
          || ((Project)element).getSubprojects().size() > 0
        )
      )
      {
        return Boolean.TRUE;
      }
      return super.hasChildren(element);
    }
  }

and used like this

private TreeViewer init(Composite parent, 
  Foundation foundation)
{
  TreeViewer viewer = new TreeViewer(parent);
  ObservableListTreeContentProvider cp = 
    new ObservableListTreeContentProvider(
      new TreeFactoryImpl(), 
      new TreeStructureAdvisorImpl()
  );
  viewer.setContentProvider(cp);
  
  // rest of viewer setup
}

Createing the CellLabelProvider
To get such a nice looking tree we can’t use a simple CellLabelProvider (e.g. ColumnLabelProvider is a simple implementation of it) but need one with more features. Another CellLabelProvider implementation is the StyledCellLabelProvider which uses owner-draw to draw StyledText-Strings in the tree.

JFace-Databinding doesn’t provide an implementation for StyledCellLabelProvider out of the box so I had to write my own one but that’s not too hard. The only thing which has to be done is to attach a listener to IObservableMap(s) to observe attributes of all tree elements and update the viewer if one of them changes.

private class TreeLabelProviderImpl 
  extends StyledCellLabelProvider
{
  private IMapChangeListener mapChangeListener = 
    new IMapChangeListener()
    {
      public void handleMapChange(MapChangeEvent event)
      {
        Set<?> affectedElements = 
          event.diff.getChangedKeys();
        if (!affectedElements.isEmpty())
        {
          LabelProviderChangedEvent newEvent = 
            new LabelProviderChangedEvent(
              TreeLabelProviderImpl.this, 
              affectedElements.toArray()
          );
          fireLabelProviderChanged(newEvent);
        }
      }
    };

  public TreeLabelProviderImpl(
    IObservableMap... attributeMaps)
  {
    for (int i = 0; i < attributeMaps.length; i++)
    {
      attributeMaps[i].addMapChangeListener(
        mapChangeListener
      );
    }
  }

  @Override
  public String getToolTipText(Object element)
  {
    return "#dummy#";
  }

  @Override
  public void update(ViewerCell cell)
  {
    if (cell.getElement() instanceof Project)
    {
      Project p = (Project)cell.getElement();

      StyledString styledString = new StyledString(
        p.getShortname()!=null ? p.getShortname():"*noname*",
        null
      );
      String decoration = " (" + 
        p.getCommitters().size() + " Committers)";
      styledString.append(
        decoration, 
        StyledString.COUNTER_STYLER
      );
      cell.setText(styledString.getString());
      cell.setImage(projectImage);
      cell.setStyleRanges(styledString.getStyleRanges());
    }
    else if (cell.getElement() instanceof CommitterShip)
    {
      Person p = (
        (CommitterShip)cell.getElement()
      ).getPerson();
      String value = "*noname*";
      if (p != null)
      {
        value = p.getLastname() + ", " + p.getFirstname();
      }
      StyledString styledString = new StyledString(
        value, null);
      cell.setText(styledString.getString());
      cell.setForeground(
        cell.getControl().getDisplay().getSystemColor(
          SWT.COLOR_DARK_GRAY
        )
      );
      cell.setImage(committerImage);
      cell.setStyleRanges(styledString.getStyleRanges());
    }
  }
}

and used like this

private TreeViewer init(Composite parent, 
  Foundation foundation)
{
  TreeViewer viewer = new TreeViewer(parent);
  ObservableListTreeContentProvider cp = 
    new ObservableListTreeContentProvider(
      new TreeFactoryImpl(), 
      new TreeStructureAdvisorImpl()
  );
  viewer.setContentProvider(cp);

  IObservableSet set = cp.getKnownElements();
  IObservableMap[] map = new IObservableMap [4];

  map[0] = EMFProperties.value(
    ProjectPackage.Literals.PROJECT__SHORTNAME
  ).observeDetail(set);

  map[1] = EMFProperties.value(
    ProjectPackage.Literals.PROJECT__COMMITTERS
  ).observeDetail(set);

  map[2] = EMFProperties.value(
      FeaturePath.fromList(
        ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
        ProjectPackage.Literals.PERSON__FIRSTNAME)
      ).observeDetail(set);

  map[3] = EMFProperties.value(
      FeaturePath.fromList(
        ProjectPackage.Literals.COMMITTER_SHIP__PERSON, 
        ProjectPackage.Literals.PERSON__LASTNAME)
      ).observeDetail(set);

  viewer.setLabelProvider(new TreeLabelProviderImpl(map));
  // Further viewer setup

The last step is to set the input of the viewer which has to be an IObservableList which is in our example all top-level projects who are restored in the foundation instance.

IEMFListProperty projects = EMFProperties.list(
  ProjectPackage.Literals.FOUNDATION__PROJECTS
);
viewer.setInput(projects.observe(foundation));

There is even more code in the ProjectExplorerPart e.g. a customized ColumnViewerToolTipSupport to create the nice looking ToolTips when hovering items of the tree as well as some RCP-Code to allow plugin.xml to contribute a context menu but that’s standard JFace and RCP code.

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.

Galileo: EMF-Databinding – Part 1

Creation of the Domain-Model

Before we can start with the domain model for our application we need to know
what the model is going to be for. So let me explain the application we are
going to write in this blog series:

Suppose the Eclipse Foundation hired you to write an application which allows them to administrate their committer and project metadata.

We now need to create a domain model of this minimal information given
which could potentially look like this:
Screen shot of the Ecore-Editor
Let’s take a closer look on the domain model objects:

  • Foundation
    Foundation Class
    The root of the domain model is the foundation which has 2 list properties:

    • projects: top level projects like EMF, Technology, Platform, …
    • persons: persons who take a part in one ore multiple projects
  • Project
    Project Class
    An Eclipse project has many different properties from the project start to the project end date to the url of the homepage. The most interesting ones are:

    • subprojects: a project can have subprojects who can then itself have subprojects
    • parent: a project has one parent pro ject (beside the top-level ones) – this means projet-subprojects relation is modeled as a bidirectional relationship
    • projectleads: a projet can have multiple project leads
    • committers: a project has multiple committers
  • CommitterShip
    Committership Class
    People who are committers on a project get a so called committership which has a start and end date. The most interesting of them are:
    • project: the project the committership is for – this means once more that the committership-project relation is modeled as a bidirectional relationship
    • person: the person who holds the committership
  • Person
    Person Class
    The “real” person who gets committer or project lead on a project. The field of interest here is:
    • committerships: which holds all committerships a person has this means that the committership-person relation is also once more modeled as a bidirectional relationship

To get a highlevel overview above the model and how the classes are related together the Class-Diagram helps a lot:
Ecore-Class-Diagram
As you noticed I modeled many of the relations as bidirectional relations. This is not strictly need everywhere because some of the relations like project-subproject have an implicit parent relation because they are containments and hence eContainer() could be used to access the parent. Still when it comes to databinding such a containment relationship doesn’t help because there’s no feature you can use to navigate from child to container but only the call to the method eContainer().

EMF also provides an editor to create an instance of your Ecore-Model and save it to XMI which makes it easy for us to create test data. An example of an XMI looks like this:
XMI-File