Flexible calendar control in SWT

I’ve been in need for Week-Calendar view to display appointments but could not find anyone that completely fit my needs to I implemented a custom one.

This short screen cast shows what I’ve today.

The control is backed by a replaceable service interface to retrieve and store appointments so it can be connected to fairly any kind of backend store, for my needs a plain XMI-Store was enough but one might want something more fancy can connect it most likely any calendar-service e.g. by using REST, … .

JFace Viewers in a Java8 world – Part 1 ListViewer

Most people who write RCP applications today know about JFace Viewers and in my opinion the Viewer framework is one of the best programming patterns that came out of Eclipse – easy to understand, easy to apply but still powerful.

Still working with viewers in a world of Java8 feels really bad because of 2 things:

  • JFace Viewers don’t have generics and useage of Object[] in the API
  • JFace APIs does not use SAM-Types

There’s a google summer of code project that tries to add generics to the JFace Viewer API but this does not change the 2nd IMHO as important part – the API feels alien in a Java8 world of lambda expressions and method references.

A few weeks back a discussion came up at the GEF mailing list in conjunction with GEF4 who thought API adopting the JFace API to setup graph viewers but that brought up a 3rd problem – JFace Viewer does not completely hide the SWT API and because GEF4 is designed to be widget toolkit agnostic then JFace Viewer API.

So the requirements for a revised JFace API is:

  1. Use generics to provide type safety
  2. Make use of SAM types
  3. Do not depend of any toolkit technology

Today I had to make a break from my day work and thought about how an Viewer 2.0 would look like and this what I came up with:

Viewer.java

/**
 * Base interface of all viewers
 * 
 * @param <O>
 *            the domain object representing a row
 * @param <I>
 *            the input to the viewer
 * @param <C>
 *            the content provider responsible to translate the input into the
 *            internal structure
 */
public interface Viewer<O, I, C extends ContentProvider<O, I>> {
  public void setContentProvider(
    @NonNull Supplier<@NonNull C> contentProvider);

  public void setInput(@NonNull Supplier<@NonNull I> input);
}

You notice that the content-provider and input are not set directly but through suppliers, the reason for that is the input most of the time is not created next to viewer but through a method call into the business layer and for the content provider one often uses a factory to reuse content provider implementations.

So the 2nd and 3rd step of setting up Viewers 2.0 would look like this:

public class Demo {

  private void setup(ListViewer<Person,List<Person>,ContentProvider<Person, List<Person>>> viewer) {
    // ... (setup of labels, ...)
    viewer.setContentProvider(
      ContentProviderFactory::createListContentProvider);
    viewer.setInput(this::listInput);
  }

  // ....
}

and the helper methods could look like this:

private List<Person> listInput() {
  try {
    return Arrays.asList(
      new Person(false, "Tom", "Schindl", format.parse("01.05.1979")),
      new Person(true, "Maria", "Musterfrau", format.parse("01.05.1970"))
    );
  } catch (ParseException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }
  return Collections.emptyList();
}
public class ContentProviderFactory {
  public static <O> ContentProvider<O, List<O>> createListContentProvider() {
    return new ContentProvider<O, List<O>>() {
      @Override
      public List<O> getRootElements(List<O> input) {
        return input;
      }
    };
  }

  // ... more factory
}

Let’s go on with the translation of the domain element into information a the viewer can present so we need to take a look at the ListViewer interface:

public interface ListViewer<O, I, C extends ContentProvider<O, I>> extends Viewer<O, I, C> {
  /**
   * Translate the domain object into a string
   * 
   * @param converter
   *            the converter
   * @return the list viewer
   */
  public ListViewer<O, I, C> textProvider(
    Function<@NonNull O, @Nullable String> converter);

  /**
   * Translate the domain object into a style information to style the cell
   * and its contents e.g. background color
   * 
   * @param converter
   *            the converter
   * @return the list viewer
   */
  public ListViewer<O, I, C> styleProvider(
    Function<@NonNull O, @Nullable String> converter);

  /**
   * Translate the domain object into a style ranges
   * 
   * @param converter
   *            the converter
   * @return the list viewer
   */
  public ListViewer<O, I, C> textStyleRangeProvider(
    Function<@NonNull O, @NonNull List<@NonNull StyleRange>> converter);

  /**
   * Translate the domain object into an image definition
   * 
   * @param converter
   *            the converter
   * @return the list viewer
   */
  public ListViewer<O, I, C> graphicProvider(
    Function<@NonNull O, @Nullable String> converter);

This makes our complete setup look like this:

public class Demo {

  private void setup(ListViewer<Person,List<Person>,ContentProvider<Person, List<Person>>> viewer) {
    viewer
     .textProvider(this::personFullText)
     .graphicProvider(this::genderImage);

    viewer.setContentProvider(
      ContentProviderFactory::createListContentProvider);
    viewer.setInput(this::listInput);
  }

  // ....
}

where the textProvider and graphicProvider-Function look like this:

private String personFullText(Person p) {
  return p.getFirstname() + "," + p.getLastname() 
   + "("+format.format(p.getBirthdate())+")";
}
private String genderImage(Person p) {
  return p.isFemale() ? "female.png" : "male.png";
}

That’s it for today – in the next post I’ll show you how a revised TableViewer API could look like

JFace-Viewer and Eclipse Databinding with > 10.000 Objects

Inspired by a bugzilla-discussion where virtual support for the nebula grid. I thought I blog a bit about Eclipse Databinding when it comes to big datasets.

The standard way used looks like this:

IObservable list = // ... a huge list of items

TableViewer viewer = new TableViewer(parent,SWT.BORDER|SWT.H_SCROLL|SWT.V_SCROLL);
ObservableListContentProvider cp = new ObservableListContentProvider();
viewer.setContentProvider(cp);

TableViewerColumn column = new TableViewerColumn(viewer,SWT.NONE);
column.getColumn().setText("Firstname");
column.getColumn().setWidth(200);
column.setLabelProvider(new ObservableMapCellLabelProvider(
    BeanProperties.value("firstname").observeDetail(cp.getKnownElements())
  )
);

TableViewerColumn column = new TableViewerColumn(viewer,SWT.NONE);
column.getColumn().setText("Lastname");
column.getColumn().setWidth(200);
column.setLabelProvider(new ObservableMapCellLabelProvider(
    BeanProperties.value("lastname").observeDetail(cp.getKnownElements())
  )
);

viewer.setInput(list);

Executing this code with the following item count shows the following figures:

Object count Time to fill (in ms)
10,000 1,178
30,000 2,793
100,000 17,689
200,000 62,689

So as we might have guess the performance degrades with the number of items with put in the viewer. In SWT you normally get the advise to use SWT.VIRTUAL but I don’t think that will address all your problems. One of them is the freaking high number of Listeners attached to your domain-objects. For 200.000 items above Eclipse Databinding has created 400.000 listeners (200.000 rows x 2 columns) not think about what would have happened if you’d have 10 columns.

So in reality there are 2 problems we have to address:

  • get the time of the initial creation down
  • only attach listeners only to those elements visible in the viewer

As outlined for SWT-Table you could address the 1st problem using SWT.VIRTUAL but for the 2nd there’s no solution. For Nebula-Grid the situation is different you don’t have SWT.VIRTUAL but instead of that some much more powerful. You can attach a listener and get informed about all visible cells so we can address both problems.

Let’s first look at the figures:

Object count Time to fill (in ms)
Original Range tracking
10,000 1,178 111
30,000 2,793 246
100,000 17,689 518
200,000 62,689 1,298

So the numbers have improved dramatically but the other really nice thing is that because by viewer shows in my test e.g. only 24 rows I have a total of 48 listeners attached to my domain objects.

The code is not really complex:

public class ViewOptimized extends ViewPart {
  public static final String ID = "at.bestsolution.grid.databinding.view";

  private GridTableViewer viewer;
	
  static final String ITEM_VISIBLE = "ITEM_VISIBLE";
	
  private static int LISTENERCOUNT = 0;
	
  public static class Person {
    private PropertyChangeSupport support = new PropertyChangeSupport(this);
		
    private String firstName;
    private String lastName;
		
    public void addPropertyChangeListener(PropertyChangeListener listener) {
      support.addPropertyChangeListener(listener);
      LISTENERCOUNT++;
    }
		
    public void removePropertyChangeListener(PropertyChangeListener listener) {
      support.removePropertyChangeListener(listener);
      LISTENERCOUNT--;
    }

    public Person(String firstName, String lastname) {
      this.firstName = firstName;
      this.lastName = lastname;
    }
		
    public String getFirstName() {
      return firstName;
    }

    public void setFirstName(String firstName) {
      support.firePropertyChange("firstName", this.firstName, 
        this.firstName = firstName
      );
    }
		
    public String getLastName() {
      return lastName;
    }

    public void setLastName(String lastName) {
      support.firePropertyChange("lastName", this.lastName, 
        this.lastName = lastName
      );
    }
  }

  public void createPartControl(Composite parent) {
    parent.setLayout(new GridLayout());
    this.viewer = new GridTableViewer(parent);
    this.viewer.getGrid().setHeaderVisible(true);
    this.viewer.getGrid().setLayoutData(new GridData(GridData.FILL_BOTH));
		
    viewer.setContentProvider(new ObservableListContentProvider());
    ObservableSet obserableSet = new WritableSet();
		
    GridVisibleRangeSupport rangeSupport = 
      GridVisibleRangeSupport.createFor(viewer.getGrid());
    rangeSupport.addRangeChangeListener(
      new VisibleRangeListenerImpl(viewer,obserableSet)
    );
		
    {
      GridViewerColumn col = new GridViewerColumn(viewer, SWT.NONE);
      col.getColumn().setText("Firstname");
      col.getColumn().setWidth(200);
      col.setLabelProvider(new LazyColumnLabelProvider(
        BeanProperties.value("firstName").observeDetail(obserableSet))
      );
    }
		
    {
      GridViewerColumn col = new GridViewerColumn(viewer, SWT.NONE);
      col.getColumn().setText("Lastname");
      col.getColumn().setWidth(200);
      col.setLabelProvider(new LazyColumnLabelProvider(
        BeanProperties.value("lastName").observeDetail(obserableSet))
      );
    }

    final ObservableList list = new WritableList();
    for( int i = 0; i < ViewOriginal.TOTAL_ITEMS; i++ ) {
      list.add(new Person("Tom " + i,"Schindl"));
    }
		
    Composite composite = new Composite(parent, SWT.NONE);
    composite.setLayout(new RowLayout());
		
    {
      Button b = new Button(composite, SWT.PUSH);
      b.setText("Populate it");
      b.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
          long t1 = System.currentTimeMillis();
          viewer.setInput(list);
          long t2 = System.currentTimeMillis();
          System.err.println("Rendering table took: " + (t2 - t1));
        }
      });
    }
		
    {
      Button b = new Button(composite, SWT.PUSH);
      b.setText("Modify First Object");
      b.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
          ((Person)list.get(0)).setFirstName("Hello World");
        }
      });
    }

    {
      Button b = new Button(composite, SWT.PUSH);
      b.setText("Stats");
      b.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
          System.err.println("Listeners: " + LISTENERCOUNT);
        }
      });
    }
  }

  public void setFocus() {
    viewer.getControl().setFocus();
  }

  public static class LazyColumnLabelProvider extends 
    ObservableMapCellLabelProvider {

    public LazyColumnLabelProvider(IObservableMap attributeMap) {
      super(attributeMap);
    }
		
    @Override
    public void update(ViewerCell cell) {
      Object visibleCheck = cell.getItem().getData(ITEM_VISIBLE);
      if( visibleCheck != null && ((Boolean)visibleCheck).booleanValue() ) {
        super.update(cell);
      }
    }
  }

  public static class VisibleRangeListenerImpl implements 
    VisibleRangeChangedListener {

    private ObservableSet obserableSet;
    private GridTableViewer viewer;
		
    public VisibleRangeListenerImpl(GridTableViewer viewer, 
      ObservableSet obserableSet
    ) {
      this.viewer = viewer;
      this.obserableSet = obserableSet;
    }
		
    @Override
    public void rangeChanged(RangeChangedEvent event) {
      for( GridItem i : event.removedRows ) {
        i.setData(ITEM_VISIBLE,Boolean.FALSE);
        obserableSet.remove(i.getData());
      }
			
      for( GridItem i : event.addedRows ) {
        i.setData(ITEM_VISIBLE,Boolean.TRUE);
        obserableSet.add(i.getData());
        viewer.update(i.getData(), null);
      }
    }	
  }
}

To round up the numbers here are those you get with plain SWT-Table with and without VIRTUAL:

Object count Time to fill (in ms)
Nebula Original Nebula Range tracking SWT Original SWT VIRTUAL
10,000 1,178 111 1,661 919
30,000 2,793 246 4,508 2,547
100,000 17,689 518 23,473 17,659
200,000 62,689 1,298 72,578 59,711

Teneo, OSGi and Javaassist for proxy loading

Yesterday I’ve debugged to get javaassist working with teneo which gave us headaches in the past so we simply turned it off which isn’t really a problem to us but I simply wanted to know why it didn’t worked.

As very often when hibernate and OSGi meet each other this is a problem of classloading and class-visibility.

A normal bundle layout for a Teneo-Hibernate application looks like this:

  • org.my.model: Holds your EMF-Model
  • org.hibernate: Holds the hibernate libraries with its dependencies (one of them is javassist)
  • org.eclipse.emf.teneo.hibernate: The Teneo Hibernate integration

The problem is that the classloader used by javaassist is the BundleClassLoader from org.my.model so from there it needs to look up 2 other important classes when it comes to proxy creation:

  • org.eclipse.emf.teneo.hibernate.mapping.internal.TeneoInternalEObject
  • javassist.util.proxy.ProxyObject

The first one you can fix by adding org.eclipse.emf.teneo.hibernate as an optional depdency on your org.my.model not really nice but well it is the only possibility.

The second one is similar but the solution is even worse because it is NOT sufficient to add org.hibernate as a required bundle because the Bundle-Classloader will not know that org.hibernate provides javassist.util.proxy because org.hibernate doesn’t export it.

So the only solution I found was to import the org.hibernate bundle coming shipped by Martin Taal from elver.org and add the exports in question.

How to apply the e4 programming model to 3.x

So while I could only encourage everyone to use the Eclipse 4.1 Application Platform (EAP) to write UI-applications, this post is for those who have existing 3.x code which they want to refactor step by step to run on EAP.

So if you ever looked at how EAP are developed the first thing you notice is the new programming model or better said the paradigm shift happing because you use DI and Services instead of subclassing and use of statics. So if you want to move your code gradually from a 3.x development model to EAP what you need is a bridge that allows you to run the refactored UI-Code in your existing 3.x runtime.

This is exactly where the Tools Bridge feature as part of the e4 updatesite might catch your attention. It allows you to use the e4 programming model inside the 3.x runtime. All you need to do to get started is to install this feature into your 3.x workspace.

Now you are able to write code that follows the e4 programming model and hence runs natively on the EAP (=without any compat layer). All you need to do to integrate your code afterwards is to subclass one of the generic ViewPart/EditorPart classes:

  • DIViewPart
  • DISaveableViewPart
  • DIEditorPart

who’ll act as the integration points into Eclipse 3.x. Now you might be confused, right? So just checkout the git-repo with a view i’ve written which works inside 3.x and 4.x and brings a feature into Eclipse I always wanted to have. It shows all images in a folder as a preview.

Here’s the view in a running 3.7 RC4:

And here in a 4.1 RC4:

You can naturally also install the view into your 3.x/4.x Eclipse Install:

For 3.x:

  • Add the e4 update site as shown above
  • Get the p2repo and add it to your update configuration

For 4.x:

  • Get the p2repo and add it to your update configuration

Please note that there’s a bug in the plugin version of one of the plugins from e4 so you won’t be able to upgrade after having installed it so only give it a try on an installation which you have no problem with reverting.

Final note: The bridge I introduced here has been built for the e4 modeltooling but will quite likely get more customers in 4.2 because we plan to refactor parts of the exiting platform code to be useable in EAP applications so it might get a key component in 4.2.

Enhanced RCP: Reuse of WritableList in JFace-Viewers

Just because I’ve been running into this today. I thought it might be interesting to others as well. The following is a shortened version of a code I’ve been using in an RCP-Application.

public class CreateItemsHandler {
   @Execute
   public void execute(IObservableList companies, 
                      @Named(IServiceConstants.ACTIVE_SHELL) Shell shell) {
       DialogImpl i = new DialogImpl(shell,companies);
       i.open();
   }

   class DialogImpl extends TitleAreaDialog {
      private final IObservableList companies;

      // ....
      public Composite createDialogArea(Composite parent) {
        // ....
        TreeViewer v; 
        // ....
        v.setContentProvider(
          new ObservableListTreeContentProvider(new ObservableFactoryImpl(),
          new StructureAdvisorImpl())
        );
        v.setInput(companies);
      }
   }; 
}

Does someone spot the error? I guess not because from it is not visible at all without looking into the implementation of ObservableListTreeContentProvider. The problem in there is that when the viewer is disposed it disposes also the IObservableList passed into it so when the handler is executed the second time you’ll run into problems.

The solution to the problem though is quite simple by making the input call look like this:

 v.setInput(new DecoratingObservableList(companies,false));

This way your original IObservableList is not disposed but only the decorator.

Enhanced RCP: How views can communicate – The e4 way

Some weeks ago I published how views can communicate using the EventAdmin-Service. To get things working in an 3.x application one has to write some glue code but more importantly one has to know about all those nifty things about event publishing, getting access to OSGi-Services, … .

One of the main topics of the Eclipse 4 Application Platform was to make easy things easy by removing the need to know about all the concepts by hiding them using DI. The service responsible to deliver events in application built on top the Eclipse 4 Application Platform is the EventBroker-Service:

package org.eclipse.e4.core.services.events;

public interface IEventBroker {
	public String DATA = "org.eclipse.e4.data"; //$NON-NLS-1$

	public boolean send(String topic, Object data);
	public boolean post(String topic, Object data);

	public boolean subscribe(String topic, EventHandler eventHandler);

	public boolean subscribe(String topic, String filter, EventHandler eventHandler,
			boolean headless);

	public boolean unsubscribe(EventHandler eventHandler);
}

This is all we need to know (assuming we have already understood how DI-works) to implement our sender and receiver views:

public class SenderView {
  @Inject
  private IEventBroker eventBroker;
  private Button b;

  @PostConstruct
  void init(Composte parent) {
    parent.setLayout(new GridLayout());
    b = new Button(parent, SWT.PUSH);
    b.setText("Send Event");
    b.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        Date d = new Date();
        eventBroker.send("viewcommunication/syncEvent",d);
        eventBroker.post("viewcommunication/asyncEvent", d);
      }
    });
  }

  @Focus
  void focus() {
    b.setFocus();
  }
}

These are some fewer lines of code which is good but IMHO the more important fact is that you are independent from OSGi running now so the code you have there is much easier testable by simply mocking IEventBroker!

Let’s look now at the receiver side of the story. If you take a look at the IEventBroker you see the subscribe methods which allows us to subscribe to various topics so we could as a first step implement it like this:

public class ReceiverView {
  @Inject
  private IEventBroker eventBroker;
  private TableViewer viewer;

  @PostConstruct
  public void init(final Composite parent) {
    parent.setLayout(new FillLayout());
    viewer = new TableViewer(parent);
    viewer.getTable().setHeaderVisible(true);
    viewer.getTable().setLinesVisible(true);
    viewer.setLabelProvider(new ColumnLabelProvider() {
      @Override
      public String getText(Object element) {
        return DateFormat.getDateTimeInstance().format(element);
      }
    });

    EventHandler handler = new EventHandler() {
      public void handleEvent(final Event event) {
        if( parent.getDisplay().getThread() == Thread.currentThread() ) {
          viewer.add(event.getProperty(IEventBroker.DATA));
        } else {
          parent.getDisplay().syncExec(new Runnable() {
            public void run() {
              viewer.add(event.getProperty(IEventBroker.DATA));
            }
          });
        }
      }
    };
    eventBroker.subscribe("viewcommunication/*",handler);
  }

  @Focus
  void focus() {
    viewer.getTable().setFocus();
  }
}

This is once more making your code looser coupled because you are independent from OSGi running but we can do even better by fully leveraging the Eclipse DI-Framework. By default Eclipse DI injects data it has stored in its IEclipseContext (you can think of the IEclipseContext as a Map where the value is thing that gets injected into you “POJO”).

The important thing for us is that one can extend Eclipse DI to consult other resources like e.g. Preferences (@Preference) and e.g. Event-Data (@EventTopic) – a blog post explaining how this works will follow hopefully soon.

So we can rewrite our receiver code like this:

public class ReceiverView {
  private TableViewer viewer;

  @PostConstruct
  public void init(final Composite parent) {
    parent.setLayout(new FillLayout());
    viewer = new TableViewer(parent);
    viewer.getTable().setHeaderVisible(true);
    viewer.getTable().setLinesVisible(true);
    viewer.setLabelProvider(new ColumnLabelProvider() {
      @Override
      public String getText(Object element) {
        return DateFormat.getDateTimeInstance().format(element);
      }
    });
  }

  @Inject
  void eventReceived(@EventTopic("viewcommunication/*") Date date) {
    Display d = viewer.getControl().getDisplay();
    if( d.getThread() == Thread.currentThread() ) {
      viewer.add(date);
    } else {
      parent.getDisplay().syncExec(new Runnable() {
        public void run() {
          viewer.add(date);
        }
      });
    }
  }

  @Focus
  void focus() {
    viewer.getTable().setFocus();
  }
}

[Update 2011-02-07] – Start
But we can do even better so that we don’t need to take of the UI-Thread syncing all we need to do is use another annotation:

@Inject
  void eventReceived(@UIEventTopic("viewcommunication/*") Date date) {
    viewer.add(date);
  }

Now the DI-Framework takes care of the Event loop syncing. Thanks to Brian de Alwis for pointing this out.
[Update 2011-02-07] – End

If you are interested in how you can use things like this in your Eclipse 3.x applications and you are going to be at EclipseCon you should come to my talk about “Singlesourcing for Eclipse 4.x and Eclipse 3.x