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:
- Use generics to provide type safety
- Make use of SAM types
- 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
I applaud the effort to modernize the Viewer API, and I think you’re heading in the right direction. Some comments about your suggestion:
– Using generics to make the viewer more type-safe is a good idea. I suggest swapping the two first type parameters, so you can read it as mapping from the input (type) to the element (type). Same thinking should be applied to ContentProvider, i.e. swap O and I.
– It is not clear to me why you need the third parameter. When do you need to limit the type of the ContentProvider to something different than ContentProvider?
– Have you thought about including the selection (i.e. user input) type in the parameter list and adding a getSelection (userInput) method (may default to the viewer input)? Clearly, some viewers, like the TreeViewer, provides selection input of many types, but it is often the same type or you have a common supertype for all nodes. This may perhaps also be coupled with a modernization of the SelectionListener interface.
– Using SAM types is a good idea!
– The setContentProvider method setter takes a ContentProvider supplier and not just a ContentProvider. This makes it more complex and should be avoided in the interface, since this forces the complexity onto all implementation and users. If needed, you can overload the method with the supplier variant in the implementation e.g. AbstractViewer. Of course, you could reverse this, use a supplier in the interface and overload with the simpler variant in the implementation, but I think the interface should be simplified as much as possible.
– I’m not sure about including fluent methods in the ListViewer interface. Although there are conventions for fluent methods, I don’t agree they are broadly accepted as a requirement that should be included in an interface. Since this is used for initialization, typically after a new, you might as well put these methods in the implementation, e.g. in an AbstractListViewer
Hallvard you are free to comment on and join the fun at https://bugs.eclipse.org/bugs/show_bug.cgi?id=440425 – the sources are currently at http://git.eclipse.org/c/efxclipse/org.eclipse.efxclipse.git/tree/experimental/viewer and have already changed slightly e.g. the ContentProvider is not collection based anymore