Desktop Java

Deferred Fetching of Model Elements with JFace Viewers

Model elements displayed by Eclipse JFace Viewers sometimes take a considerable amount of time to load. Because of this the workbench provides the type IDeferredWorkbenchAdapter to fetch such model elements in background. Unfortunately this mechanism seems to be supported only for AbstractTreeViewer derivates via the DeferredTreeContentManager.

Hence I developed a generic DeferredContentManager of my own… It enables background loading for all StructuredViewer types that allow to add and remove model elements. And in this post I explain how it works and how it can be used.

 
 
In the need for (re-)use of background fetching with a TableViewer, I solely found an old and unresolved platform bug regarding this topic. But I doubt that issue’s proposed solution of implementing an additional content manager for table viewers would be very smart anyway. So I decided to give a selfmade generic solution that is based on the concepts of the available tree specific implementation a try.

Deferred Fetching of Content with JFace Viewers

The basic principle of dealing with long loading model elements in JFace Viewers is simple. Rather than fetching the content within IContentProvider#getElements(Object) directly, data retrieval is delegated to a particular adapter that performs it in a background job.

Moreover, the delegating getElements(Object) implementation returns a place holder. This is shown by the viewer as long as data loading takes place. In the meanwhile collected data gets forwarded to an update job. The latter appends the elements to the structured viewer. The update job is a derivate of UIJob since SWT widget access is only allowed from code executed by the UI Thread.

Finally when background fetching has been completed a cleanup job removes the placeholder.

Deferred fetching of content should not be confused with lazy loading of elements using the SWT.VIRTUAL flag. While there are similarities between both approaches, virtual table and trees are generally useful for partial on-demand loading of large datasets.

Deferred loading is helpful for reasonable sized datasets, which nevertheless might be time consuming to retrieve and therefore would block the UI thread. Consider fetching of remote data for example. And in case you wonder, both approaches are of course mutally exclusive

IDeferredWorkbenchAdapter

From the developer’s point of view the IDeferredWorkbenchAdapter is the way to go. It is an extension of IWorkbenchAdapter, which in general is responsible to ‘provide visual presentation and hierarchical structure for workbench elements, allowing them to be displayed in the UI without having to know the concrete type of the element’ – as stated by its javadoc.

The extension declares additional methods to support deferred fetching of children of a given data element and can be registered by an adapter factory. Consider a simple pojo that serves as model element for example:

public class ModelElement {
  [...]
}

In order to abstract visual presentation and background loading from the domain classes provide an appropriate adapter implementation…

public class ModelElementAdapter
  implements IDeferredWorkbenchAdapter
{
  [...]
}

… and map both types together using an adapter factory:

public class ModelElementAdapterFactory
  implements IAdapterFactory
{

  @Override
  public Object getAdapter( Object adaptableObject, Class adapterType ) {
    return new ModelElementAdapter();
  }

  @Override
  public Class[] getAdapterList() {
    return new Class[] { ModelElement.class };
  }
}

For more information about using IAdaptable, IWorkbenchAdapter and IAdaptableFactory you might have a look at How do I use IAdaptable and IAdapterFactory?. Sadly the default workbench content and label providers expects the model elements to implement IAdaptable. However this can be circumvented by using custom providers.

The following test sketch verifies that element adaption works as expected:

@Test
public void testAdapterRegistration() {
  IAdapterManager manager = Platform.getAdapterManager();
  ModelElementAdapterFactory factory = new ModelElementAdapterFactory();

  manager.registerAdapters( factory, ModelElement.class );
  Object actual = manager.getAdapter( new ModelElement(), ModelElement.class );

  assertThat( actual )
    .isInstanceOf( ModelElementAdapter.class );
}

Now it is about time to implement the data retrieval functionality of the ModelElementAdapter. This is done in the fetchDeferredChildren method:

@Override
public void fetchDeferredChildren(
  Object parent, IElementCollector collector, IProgressMonitor monitor )
{
  collector.add( loadData( parent ), monitor );
}

private Object[] loadData( Object parent ) {
  return [...]
}

Time consuming data loading is obviously handled by the method loadData(). Adding the data elements to the IElementCollector triggers the update job mentioned above. As you can see data fetching could be devided in several steps and progress could be reported via the given IProgressMonitor.

DeferredContentManager

The last thing to do is to connect the mechanism described in this post with the viewer instance used to depict the model elements. For this purpose DeferredContentManager can adapt arbitrary viewers and delegates element retrieval to the appropriate IDeferredWorkbenchAdapter implementation.

class ModelElementContentProvider
  implements IStructuredContentProvider
{

  DeferredContentManager manager;

  @Override
  public void inputChanged(
    Viewer viewer, Object oldInput, Object newInput )
  {
    TableViewerAdapter adapter 
      = new TableViewerAdapter( ( TableViewer )viewer );
    manager = new DeferredContentManager( adapter );
  }
  
  @Override
  public Object[] getElements( Object inputElement )  {
    return manager.getChildren( inputElement );
  }

  [...]
}

A custom IStructuredContentProvider is used to adapt the viewer in its inputChanged method. The implementation of getElements delegates to the content manager, which in turn delegates element loading to the model element adapter using DeferredContentManager#getChildren.

While fetching proceeds, a placeholder element is returned to show a ‘Pending…’ label in the viewer. This is the situation shown in the title image on the left hand side. On the right side retrieval has been completed and the placeholder has been removed.

StructuredViewerAdapter

Looking at the example it becomes clear how the DeferredContentManager is able to support different viewer types. The viewer is adapted by the content manager using an suitable derivate of StructuredViewerAdapter. For the time being there are only default adapters for abstract tree- and table viewers available.

However it is straight forward to write adapters for other structured viewer types. The following snippet shows e.g. the implementation for a ListViewer:

public class ListViewerAdapter
  extends StructuredViewerAdapter
{

  public ListViewerAdapter( AbstractListViewer listViewer ) {
    super( listViewer );
  }

  @Override
  public void remove( Object element ) {
    viewer.remove( element );
  }

  @Override
  public void addElements( Object parent, Object[] children ) {
    viewer.add( children );
  }
}

Using this and replacing the table viewer by a list viewer in the example would lead to the following outcome:

list-viewer-adapter

Cool! Isn’t it?

Conclusion

This post gave an introduction of DeferredContentManager and showed how it enables background loading of model elements with different JFace Viewers. And if – after all the compelling usage explanations above – you might wonder where to get it, you will make a find at the Xiliary P2 repository. The content manager is part of the com.codeaffine.eclipse.ui feature:

In case you want to have a look at the code or file an issue you might also have a look at the Xiliary GitHub project:

Subscribe
Notify of
guest

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

0 Comments
Inline Feedbacks
View all comments
Back to top button