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