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.
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.
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 }; } }
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:
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:
https://fappel.github.io/xiliary
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:
https://github.com/fappel/xiliary
For everything else feel free to use the commenting section below.
- Xmas Clean Sheet Update (0.9) - 21. December 2021
- Clean Sheet Service Update (0.8) - 23. May 2020
- Clean Sheet Service Update (0.7) - 24. April 2020
Thanks Frank. Great article. I was just starting a quest to code something like this. Your article couldn’t have come in a better time.
I have a question about the license of your code. for me to reuse it, It would need to have some license preferably EPL.
There is no comment in the sources stating which license it is [1] but I do see a license in the root of your github repo. I think you want to decorate your sources as well with license info.
Thanks again,
Wim
[1] https://github.com/fappel/xiliary/blob/master/com.codeaffine.eclipse.ui/src/com/codeaffine/eclipse/ui/progress/Adapters.java
Hi Wim, glad you have a use for the code. All repository content is indeed EPL. But you are right, I will add the license headers ASAP. Thanks for the hint, Frank.
Hello Frank, nice article! There is not much info related to DeferredContentManager on the Internet.
I have another problem related not to Content Provider itself but Label Provider. Historically it became that my label provider is also doing time consuming operations and performing internet lookups. I am just wandering is there something similar in jface or eclipse platfrom that could be used for deferring element label lookups.
Hello Alex, glad you like the post. Unfortunately, I’m not aware of an out of the box solution for your problem. Running the long-running operation in a background job and update the item’s placeholder text after the job has been finished would be the hands-on solution that spontaneously crossed my mind.
You can take a look at the Decorator framework. This is what you are looking for,
That’s a good suggestion, I completely forgot about the Decorator framework, shame on me…
Thank you guys. It look like Decorators (http://www.eclipse.org/articles/Article-Decorators/decorators.html) is whole new exciting world for me. Will dive in it with pleasure :)
Frank, I want to pull your code into JFace. Do you have objections or want to help? I filed [1] a while ago but I’m thinking to take this on for 4.14 or 4.15.
https://bugs.eclipse.org/bugs/show_bug.cgi?id=509006
Hi Wim, go ahead. Happy that it helps JFace :-}