Roman Kennke and Mario Torre
aicas GmbH
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.
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.
FontManager class was a final class with lots of static methods in it,
mixing Solaris, Windows, and generics code. The first step of refactoring was to
make the FontManager an abstract class, with most static methods now instance
methods, and moving some platform specific things to a subclass
(DefaultFontManager only for now).
SwingUtilities2.isLocalDisplay(), there were some platform checks to
determine if a display is local or not. Worse, in the Linux/Solaris case, it uses
reflection to directly access X11GraphicsEnvironment. This does not work for
graphics backends on Linux that do not use the X11GraphicsEnvironment. The authors
introduced a new method isDisplayLocal() in SunGraphicsEnvironment, which can
be implemented by the backend. When a backend does not use SGE, it is assumed
that the display is local.
FontManager, another set of static methods and fields
has been changed to instance methods and fields, and access to them now goes
over the factory method.
SunGraphicsEnvironment and platform specific
subclasses have been moved to FontManager and platform specific subclasses.
SunGraphicsEnvironment have
been moved to FontManager too.
RepaintManager, there were some direct casts to SunToolkit, which does not work
if the implementing Toolkit is not a SunToolkit. The authors added instanceof checks,
assuming appropriate default behaviour if the Toolkit is not a SunToolkit.
java.awt.peer.*
FontManager an interface.
Some more static methods have been converted to instance methods.
Implementation has been split out into FontManagerBase and FontConfigManager.
KeyboardFocusManager instantiate a KeyboardFocusManagerPeerProvider.
This patch resulted from discussion on one the OpenJDK mailing list.
The authors opine that the correct approach is still to make this API part of the
Toolkit Interface. Without this patch, the authors were forced to implement a
KeyboardFocusManagerPeerProvider directly in the new Toolkit, even for the
external project, despite that the interface never mandates it. While this is
clearly a hack, it is still better than the current situation, thus the patch
is included to show a cleaner interface with a proof of concept for the
createKeyboardFocusManagerPeer method in the Toolkit API.
java.awt.Window, the splashscreen was closed by using the static
method SunToolkit.closeSplashScreen(). Not only is this bad for portability
(how can a TK implement this??), it is also a duplicated piece of code, because
all the necessary functionalitly is already in SplashScreen.close().
Window now uses the official API.
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.
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.
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.
The Challenge provided a couple of interesting ideas for future projects. These are a logical continuation of the Challenge project.
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().
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.