Blog
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:
- 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