Java to Xtend
The class is of course written in Java. Even though there is no technical reason - Java and Xtend classes can coexist in the same project without any problems - I wanted to convert it to Xtend as well. I am a lazy guy. Luckily Krzysztof Rzymkowski had recently posted on the Xtend group that he had started to implement a Java to Xtend converter. It even has a web interface, and except for one slight issue with afor
-loop it worked like a charm. Great work, Krzysztof!Of course the resulting code is pretty Java-like, so I wanted to improve on it using more of the cool Xtend features. I found quite a few spots to do so, reducing the amount of code significantly and enhancing readability a lot. The remainder of this post is about this ongoing love of JavaFX and Xtend. The complete source code will likely be made open-source soon.
Dispatch Methods
The class starts with a method that only contains aninstanceof
-cascade to delegate to the conversion method for the specific subclass of Shape
the parameter has:public static String shapeToSvgString(final Shape SHAPE) { final StringBuilder fxPath = new StringBuilder(); if (Line.class.equals(SHAPE.getClass())) { fxPath.append(convertLine((Line) SHAPE)); } else if (Arc.class.equals(SHAPE.getClass())) { fxPath.append(convertArc((Arc) SHAPE)); } else if (QuadCurve.class.equals(SHAPE.getClass())) { fxPath.append(convertQuadCurve((QuadCurve) SHAPE)); } ...In Xtend I can use dispatch methods to realize this: They must have the same method and number of parameters, but different parameter types. The Xtend compiler then generates the dispatcher method with the
instanceof
-cascade automatically. So renaming the delegate methods and marking them as dispatch
made the dispatcher method obsolete. def dispatch String toSvgString(Line line) ... def dispatch String toSvgString(Arc arc) ... def dispatch String toSvgString(QuadCurve quadCurve) ...Tip: If you want to delegate from one dispatch case to another, the original methods are available with an underscore preceeding their name.
Templates
An SVG path is kind of a cryptic string. In the Java code it is assembled using aStringBuilder
, e.g.final StringBuilder fxPath = new StringBuilder(); fxPath.append("M ").append(CENTER_X).append(" ") .append(CENTER_Y - RADIUS).append(" "); fxPath.append("C ") .append(CENTER_X + CONTROL_DISTANCE) .append(" ").append(CENTER_Y - RADIUS).append(" ") .append(CENTER_X + RADIUS).append(" ") .append(CENTER_Y - CONTROL_DISTANCE) .append(" ") .append(CENTER_X + RADIUS).append(" ") .append(CENTER_Y).append(" "); ...That's the best you can do with Java. In Xtend we have template expressions - multiline strings which can be interrupted with values from expressions. Even
IF
-conditions and FOR
-loops are supported. With carefully chosen regular expressions for find/replace and some manual fine-tuning the above becomes nicely readable'''M «centerX» «centerY - radius» C «centerX + controlDistance» «centerY - radius» «centerX + radius» «centerY - controlDistance» «centerX + radius» «centerY» ...
Switch Expression
I found another finer-grainedinstanceof
-cascade in the convertPath
method:final StringBuilder fxPath = new StringBuilder(); for (PathElement element : PATH.getElements()) { if (MoveTo.class.equals(element.getClass())) { fxPath.append("M ") .append(((MoveTo) element).getX()).append(" ") .append(((MoveTo) element).getY()).append(" "); } else if (LineTo.class.equals(element.getClass())) { fxPath.append("L ") .append(((LineTo) element).getX()).append(" ") .append(((LineTo) element).getY()).append(" "); } else if (CubicCurveTo.class.equals(element.getClass())) { fxPath.append("C ") ...As the bodies of the
if
-statements are so simple, I decided to use Xtend's switch instead. It allows to use type guards for the cases and automatically cast the switch variable to that type inside the case's body:
val it = new StringBuilder for (element : path.elements) { switch element { MoveTo: append('''M «element.x» «element.y» ''') LineTo: append('''L «element.x» «element.y» ''') CubicCurveTo: append('''C «element.controlX1»...I could further use the operator
=>
instead of the Builder class and the operator <=>
replacing Double.compare
.Extension Import (Client Side)
From a client side, theshapeToSvgString
method is a utility method for Shapes
. In Xtend, you can import such methods using a static extension import. That makes them callable in extension syntax, as if the method was defined in the class of the first parameter. To further improve readability, I renamed the methods to toSvgString
such that I can now writeimport static extension ...ShapeConverter.* ... new Rectangle.toSvgString