OpenJDK Challenge

Portable GUI Backends (The Caciocavallo project)

Roman Kennke and Mario Torre

aicas GmbH

Introduction

In order to support the spread of Java for graphical applications, particularly in embedded systems, aicas GmbH proposed to improve the AWT and Java2D in OpenJDK to enable easier porting of AWT to new platforms.

The assumption was that despite the name of the Toolkit interface (Abstract Windowing Toolkit) and its design that provides a public API and a series of abstract classes meant to be used by the specific platforms, the current implementation in OpenJDK is a mix of platform specific code, non portable code that relies on non documented features, and code that explicitly casts back to Sun internal classes. The code simply defeats the whole purpose of having such an interface in the first place and makes porting AWT to new platforms unnecessarily hard or impossible without modifying Swing and AWT themselves.

The Project was accepted as part of the OpenJDK Challenge 2008, with the code name Caciocavallo (due to some nice dinners with the Italian cheese that the authors had together with some Sun developers at Fosdem 2008). Caciocavallo consist of two main subprojects, one called "internal" implementation and the other "external".

The Internal implementation is meant to reuse as much code as possible from the existing peers. It does it by subclassing the Sun internal classes, like SunToolkit, SunGraphicsEnvironment, SunGraphics2D. It then defines a Java2D pipeline for its rendering.

The External implementation, on the contrary, tries to not depend on internal code. This is the main source of refactoring for the AWT peers in OpenJDK. Every time a dependency was found in internal code, it has been fixed or worked around so that the code is not implementation dependent. Hence, the refactored code can now use different code paths, one "optimized", when a SunToolkit is detected, and one that leaves everything to the peer implementors.

Caciocavallo, and Caciocavallo-NG are the names of the Peers, where the NG version refers to the Internal subproject.

The project presents also an additional level of refactorisation, the Sun Font API. This is a special case, as there is no real API defined for the internal implementation of the Font system, but a final class that acts like a sort of catch all. The authors tried to refactor this class to make it possible to just "hook" in a new implementation, maybe reusing only some bits of code.

This subproject was needed to help to assure the portability of the Toolkit API, but was considered an extra task. The current status is that most of the final class FontManager was refactored into an Interface called FontManager, while the implementation was moved down to a class FontManagerBase. Also, all the code that referenced the FontConfig library (that before was part of FontManager) was moved to a different class, FontConfigManager. For the external project, this resulted in implementing a FontManager subclassing and then implementing only three methods. It is possible to get an instance of FontManager via a factory class, FontManagerFactory.

OpenJDK Patches

Another component that is part of the project is the Mercurial Patch Queue repository the project used. At the time of this writing, the MQ repository is located at the address:

http://hg.openjdk.java.net/caciocavallo/caciocavallo/
and
http://kennke.org/~hg/hgwebdir.cgi/openjdk-patches/
To help easy testing, the authors maintain a separate OpenJDK branch on a private server:
http://kennke.org/~hg/hgwebdir.cgi/openjdk-tip-b7474b739d13-caciocavallo-branch/

One can directly use this branch, cloning the entire forest instead of the single patches or the MQ repository. This branch also contains few patches to make the build possible with a GCC 4.3 compiler (as of tip b7474b739d13, as hotspot does not compile on GCC 4.3 in both Fedora and Ubuntu Linux. This problem should be fixed in more recent version, thus provided no patches to upstream).

There are several important patches from the patch queue. This list may change as further refactoring or better organisation of the patches is performed.

Getting the code

The easiest way to get the code is to clone the complete OpenJDK forest from the private repository:

hg fclone http://kennke.org/~hg/hgwebdir.cgi/openjdk-tip-b7474b739d13-caciocavallo-branch openjdk-caciocavallo

If only single patches are of interest, one can just check out the relevant patch queue:

hg clone http://kennke.org/~hg/hgwebdir.cgi/openjdk-patches/

This patch queue is only guaranteed to apply cleanly on the ChangeSet b7474b739d13 of the subrepository jdk of the official OpenJDK forest.

Building

The Caciocavallo OpenJDK forest is built just like every other OpenJDK forest, so please follow the official build instructions.

Testing the code

To test the project, one needs to download some extra code, that for various reason could not be hosted on the OpenJDK website.

As said in the introduction, the authors have two actual proof of concept implementations, named Caciocavallo and Caciocavallo-ng.

These are available on the private website:


http://kennke.org/~hg/hgwebdir.cgi

just checkout (hg clone) the specific repository needed, e.g.


# hg clone http://kennke.org/~hg/hgwebdir.cgi/caciocavallo/

or

# hg clone http://kennke.org/~hg/hgwebdir.cgi/caciocavallo-ng/

One then needs a copy of the Escher library:


# hg clone http://kennke.org/~hg/hgwebdir.cgi/escher-trunk

An X11 Server is also required. The authors tested the Escher library, as well as the default Escher Peers from GNU Classpath, on Windows using a Windows version of the X11 Server, but currenty the Windows build of OpenJDK does not take full advantage of the refactoring and is not tested.

Please, note, the peers themselves are prototypes only and may not work with all applications. For testing, the SwingDemo from GNU Classpath was used a lot, as well as the SwingSet2 demo from the JDK, and the graphical frontend of FindBugs.

The Classpath SwingDemo is available as jar package from this link:

http://www.limasoftware.net/neugens/downloads/classpath/caciocavallo/examples.jar

As an alternative, is possible to download pre-build binaries for all the software from this link: http://kennke.org/~hg/packages

To run the demo with the Caciocavallo peers, a few properties need to be passed to the java command, as shown in the following bash script:

#!/bin/sh 
# testescher.sh 
OPENJDK_DIR=/path/to/your/openjdk/build/linux-i586/j2sdk-image/ 
BOOTCLASSPATH=/path/to/escher.jar:/path/to/caciocavallo/dist/echer-peer.jar 
TOOLKIT=gnu.java.awt.peer.x.XToolkit 
GRAPHICSENV=gnu.java.awt.peer.x.XGraphicsEnvironment 
FONT_MANAGER=gnu.java.awt.peer.x.EscherFontManager 

# change to suit your needs, we like to use the classpath code 
CLASSPATH=/path/to/examples.jar
MAIN=gnu.classpath.examples.swing.Demo 

$OPENJDK_DIR/bin/java -Xbootclasspath/a:$BOOTCLASSPATH \
  -Dswing.metalTheme=steel \
  -Dawt.toolkit=$TOOLKIT -Djava.awt.graphicsenv=$GRAPHICSENV \
  -Dsun.font.fontmanager=$FONT_MANAGER -cp $CLASSPATH $MAIN 

Of course, the paths must be changed for the test environment.

Note: the escher library needs tcp access to the X11 server. It may also be necessary to allow permission with the xhost command. This is a bug in the version of Escher currently used. To workaround it, it should be enough to just run: xhost + 127.0.0.1 on the command line.

For findbugs, the authors host a modified verison, the only difference being a file named findbugs_caciocavallo in the findbugs bin directory. As the paths are hardcoded, they need to be changed at the end of the bash script:

fb_jvmargs="$user_jvmargs $debug_arg $conservespace_arg $workhard_arg 
            $user_props $ea_arg -Dswing.metalTheme=steel
            -Dsun.font.fontmanager=gnu.java.awt.peer.x.EscherFontManager
            -Dawt.toolkit=gnu.java.awt.peer.x.XToolkit
            -Djava.awt.graphicsenv=gnu.java.awt.peer.x.XGraphicsEnvironment
            -Xbootclasspath/a:/path/to/escher/build:/path/to/caciocavallo-ng/dist/echer-peer.jar" 

This verision is located at this address.

Problems and Conclusions

The proof of concept was designed with Linux in mind, due to the limited time and the big issues the project presents. The authors tried hard to make the Windows build to at least compile, preserving the original code path so that things continue to work, but this can not be guarantee at the moment. The Solaris build shares all the refactored code with the Linux one.

Caciocavallo (the external prototype) is currently not very stable. All the demos could be run, but there are some repainting issues, especially when dealing with viewports. The code for the scanline is currently disabled, this means that one cannot draw many shapes correctly. There are problems related to the implementation of the peer code that have nothing to do with the refactoring process. Also, implementing a complete new AWT/Java2D backend without using OpenJDK's pipeline and existing code is a gigantic piece of work, not something that is possible to come close in 4 months of work.

Caciocavallo-ng is much more complete, as it makes use of a lot of existing code from OpenJDK. This is currently able to run quite well the SwingSet2, the Classpath SwingDemo and Findbugs.

The authors believe that the Caciocavallo project was an important experience and want to thank Sun for giving the opportunity to work closely on this project. They learned a lot about the internals of OpenJDK, and the difficulties that developers have to face when developing on the JDK in general and Java2D/AWT/Swing in particular. They were able to spot and fix various problems in OpenJDK's AWT/Java2D implementation, and now there is an implementation that is much more portable than before.

Ideas and future development

The Challenge provided a couple of interesting ideas for future projects. These are a logical continuation of the Challenge project.

Fonts

While a significant part of the font implementation was refactored, this is by no means all that has to be done. The FontManager interface so far is only a plain refactored interface from the orginal final class, without having changed the semantics of the original code. However, this interface can not yet be considered a clean API which can serve for implementing a new font backend. It can and should be developed further into a clean interface that is actually usable.

The code for finding fonts on a particular platform is very system specific and relies on properties files, which point to the specific font files and directories. This code could (in part) make use of the FontConfig library. In fact, since FontConfig is based on a couple of XML configuration files in defined directory locations, it should not be too hard to implement a 100% Java FontConfig library, which could then be used by the Font2D code to determine the locations of fonts at runtime.

Another nice to have feature for fonts would be the ability to read fonts from input streams or generic buffers somehow, instead from memory mapped files only. This would make it easier to support fonts on platforms that do not support memory mapping files (i.e. systems without MMU), and would make it possible to provide fonts via the classpath and load them using ClassLoader.getResourceAsStream().

AWT peers based on Swing

Another future project is the implementation of a set of generic AWT peers, which would use Swing for rendering and handling the logic of the AWT components. The Caciocavallo prototypes already contain such an implementation but it has its problems (namely bad heavyweight/lightweight behaviour). In the future, the authors want to implement such peers from scratch, pulling together the XAWT architecture with ideas from the "old" Swing peers from the prototypes.