JavaFX Loves Xtend
JavaFX is Java's new rich client platform. With JavaFX it is easy to create modern graphical user interfaces. It has built-in support for visual effects and animations, supports touch-aware devices and it includes an API for data-binding and event propagation. JavaFX is the official successor to Java Swing but it will result in much better looking apps.
Xtend is a statically
typed programming language developed at Eclipse. Based on Java, Xtend
removes the syntactic noise and adds the missing bits such as
type inference, lambda expressions, extension methods or
operator overloading. It is compiled to Java code and thus
integrates seamlessly with existing libraries and frameworks.
Xtend brings the fun back to Java programming without any
trade-off in terms of compatibility. Xtend's additional features
are a perfect match to the needs of a JavaFX programmer. It will
make your code shine the same way as a JavaFX application does.
In this article, I will demonstrate that with a couple of
examples.
This article relates to Xtend version 2.4.
Event Listeners
Each JavaFX Node
has a couple
of convenience methods to react on UI events such as mouse
clicks, key strokes or multi-touch gestures. In Java a mouse
click can for example be handled with the following code:
// Java code Rectangle node = new Rectangle(); node.setOnMouseClicked(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { System.out.println(event.getButton() + " button clicked"); } });
Let's see how we can improve this code by using Xtend. First of
all, Xtend's type inference allows to skip the type when it can
be inferred from the context. So the
Rectangle
construction becomes
val node = new Rectangle
Note that semicolons can be skipped as well as empty parentheses.
Anonymous classes with a single method such as the
EventHandler
instance is a very common Java idiom. Xtend improves on this
significantly by allowing to use lambda
expressions instead. The type is inferred from the actual
context. In the example the expected type is an
EventHandler<MouseEvent>
, so the lambda expression is automatically converted to this
type:
val node = new Rectangle node.setOnMouseClicked([ MouseEvent event | println(event.getButton + "button clicked") ])
The part
MouseEvent event |
denotes the parameter list of the lambda. If the lambda has only
one parameter it can be skipped and it will be named
it
. The parameter type is also inferred from the context, in this
case it must be
MouseEvent
. Like
this
you can omit
it
as the receiver of feature call. Without the explicit parameter
the code looks like this:
val node = new Rectangle node.setOnMouseClicked([ println(getButton() + "button clicked") ])
Finally, Xtend has a shortcut
syntax for getters and setters: Instead of
getButton()
you can just write
button
. Similarly,
setOnMouseClicked(arg)
becomes
onMouseClicked = arg
. Once again this is just syntactic sugar. The accessor methods
are still called. Our final result reads as:
val node = new Rectangle node.onMouseClicked = [ println(button + "button clicked") ]
This snippet of Xtend code is completely equivalent to the Java version above. It is a lot shorter, but it is still easier to understand as all the syntactic noise has been cut out. Still everything is type-safe and well defined.
The With Operator vs Generated Builder Classes
Sometimes a JavaFX object has quite a lot of parameters to be customized. Instead of implementing a new constructor with a huge number of parameters for each use case, the JavaFX developers generated builder classes with fluent APIs, e.g.
// Java code Rectangle rectangle = RectangleBuilder.create() .width(80) .height(30) .fill(Color.BLUE) .stroke(Color.RED) .strokeWidth(1.2) .arcWidth(12) .arcHeight(12) .build();
In Xtend the generated boilerplate is not needed at all because
there is the with operator
=>
that binds the preceding argument to the parameter of a lambda
expression and executes the latter:
val rectangle = new Rectangle => [ Rectangle r | r.setWidth(80) r.setHeight(30) r.setFill(Color::BLUE) r.setStroke(Color::RED) r.setStrokeWidth(1.2) r.setArcWidth(12) r.setArcHeight(12) ]
Using the implicit
it
as closure parameter and the sugared setter call as explained
above our code becomes:
val rectangle = new Rectangle => [ width = 80 height = 30 fill = Color::BLUE stroke = Color::RED strokeWidth = 1.2 arcWidth = 12 arcHeight = 12 ]
Note that the Xtend code is shorter than the Java builder syntax even though it does not require an extra builder class. In a similar way, the with operator facilitates the creation of object trees, e.g. subtrees of JavaFX's scene-graph.
Extension Methods For High-Level Property Binding
In JavaFX, the value of a property can be derived from other
properties by means of another fluent API for the calculation.
E.g. given two
DoubleProperties
a
and
b
, you can bind a property
average
which will be automatically updated when
a
or
b
change:
// Java code average.bind(a.add(b).divide(2));
When these calculations get more sophisticated and you do a lot
of them the code becomes very unreadable. This is the right
moment to think about overloaded
operator extensions for JavaFX's
DoubleExpressions
. The code to overload an operator looks like this:
def static operator_plus(DoubleExpression a, ObservableNumberValue b) { a.add(b) }
You would usually collect all such overloading methods in a
class
DoublePropertyExtensions
and make them available using a static
extension import whenever you need them. The above Java
example becomes as simple as
import static extension my.company.DoublePropertyExtensions.* //... average << a + b / 2
Extension methods and operator overloading can also be used to
add the missing APIs for geometry calculation, e.g. to apply a Transform
to a Point3D
or to
accumulate
Transforms
. For detail please see the article on "Accumulating
JavaFX Transforms With Xtend".
Declaring Properties
JavaFX properties are usually wrapped in getters and setters
to comply with the JavaBeans
specification. For a lazily created
DoubleProperty
radius
the recommended
code looks like this:
//Java code public class Balloon { private DoubleProperty radius; private double _radius = 20; public double getRadius() { return (radius != null)? radius.get() : _radius; } public void setRadius(double value) { if (radius != null) radius.set(value); else _radius = value; } public DoubleProperty radiusProperty() { if (radius == null) radius = DoubleProperty(this, "radius", _radius); return radius; } // ... }
This is a lot of code for a simple property and a perfect use
case for a new language feature of Xtend: With Active
Annotations you can manipulate how Xtend is compiled to
Java. In our case, you could implement an Active Annotation
@FXBean
that describes the code pattern above. If an Xtend class is
annotated as
@FXBean
, this pattern is expanded for all its fields in the generated
Java code automatically by the compiler. So the Xtend version
becomes
@FXBean class Balloon { double radius = 20 //... }
The exemplary code for
@FXBean
can be found here.
Note that Active Annotations are loaded from the
classpath of the project at compile time, thus can reside in the
same workspace as the client code. They neither have to be
deployed to be used nor shipped with the application code. For
more infos on Active Annotations please refer to the
other article in the newsletter.
I hope I could convince you that JavaFX and Xtend make a perfect couple. If you want to stay up-to-date with our work on Xtend, join us on Twitter or ask your questions in our Google Group.