A Domain Specific Language for JavaFX 2.0 and Scala
Join the DZone community and get the full member experience.
Join For FreeTwo weeks ago, at the JavaOne 2010 Conference in San Francisco, there was an announcement that Oracle will no longer supporting JavaFX Script as a language. In fact, Sun.Oracle will refactor the current implementation into a Java API and framework in the 2.0.
As many have reported (and obviously mis-reported) Oracle made business and technological decisions about the future of JavaFX. In this blog, I mentioned my conversation with Brian Goetz, the simple issue was the tooling around Java the language is too high a barrier to reach in order to bring JavaFX Script up the same high quality standard. Oracle decision to discontinue in-house development of JavaFX Script was a time-to-market decision and also reflection of the experience that building the JavaFX platform is considerable more expensive than the language.
The most important innovation in the JavaFX Script language is the binding syntax. It looks incredibly simple but imposes so much hard thinking under the surface of the ice berg. JavaFX Script language is the top of the ice berg, the easy target, the white motionless mountain that sits above the sealine. Sun.Oracle are building below this and have been for a few years already. This hard to engineer especially the binding and updates of properties.
The advantages on concentrating on the bottom of the iceberg are:
- Hardware accelerated graphics architecture and pipeline (unified temporarily Java2D and Prism worlds)
- Unified scene graph architecture thorough 2d and 3d views
- A brand new Prism based applet and JNLP web startable applications, which will be deployable from the get-go
- Allow JVM language authors and enthusiasts to create DSL in their own favourite programming languages that (re-)use the forthcoming JavaFX for Java 2.0 API
It is this last one item that I concentrate here. Since JavaOne 2010, yours truly has been seriously ruminating over a possible Scala Domain Specific Language for JavaFX 2.0 for Java API, especially knowing that Sun.Oracle will deliver this functionality in 6-8 months as promised. The bad old days of Sun Microsystem loss of focus over a wide range of product of tentative monetisation are truly gone for ever, especially in Larry Ellision’s open Oracle world.
This is my speculative buy in to the JavaFX 2.0 API. I believe Sun.Oracle will choose a builder pattern for the Java API rather than the legacy JavaBeans, setters and getters. Here is the Node.java in my personal opinion:
package uk.co.xenonique.javafx; /** * Research JavaFX for Java API 2.0 * @author Peter Pilgrim, 26 September 2010 */ public abstract class Node { private boolean blocksMouse; private Bounds boundsInLocal = new Bounds(); private Bounds boundsInParent = new Bounds(); private Node clip; private boolean focused; private boolean hover; private String id; private Bounds layoutBounds = new Bounds(); private float layoutX; private float layoutY; private boolean managed; private float opacity; private boolean pressed; private Parent parent; private boolean visible; public Node() { super(); } public boolean isBlocksMouse() { return blocksMouse; } public Node setBlocksMouse(boolean blocksMouse) { this.blocksMouse = blocksMouse; return this; } public Bounds getBoundsInLocal() { return boundsInLocal; } public Bounds getBoundsInParent() { return boundsInParent; } ... public boolean isFocused() { return focused; } public boolean isHover() { return hover; } public String getId() { return id; } public Node setId(String id) { this.id = id; return this; } public Bounds getLayoutBounds() { return layoutBounds; } public float getLayoutX() { return layoutX; } public float getLayoutY() { return layoutY; } public boolean isManaged() { return managed; } public Node setManaged(boolean managed) { this.managed = managed; return this; } public float getOpacity() { return opacity; } public Node setOpacity(float opacity) { this.opacity = opacity; return this; } public Parent getParent() { return parent; } public Node setParent(Parent parent) { this.parent = parent; return this; } public boolean isPressed() { return pressed; } public boolean isVisible() { return visible; } public Node setVisible(boolean visible) { this.visible = visible; return this; } ... @Override public String toString() { return "Node{" + "blocksMouse=" + blocksMouse + ", boundsInLocal=" + boundsInLocal + ", boundsInParent=" + boundsInParent + ", clip=" + clip + ", focused=" + focused + ", hover=" + hover + ", id=" + id + ", layoutBounds=" + layoutBounds + ", layoutX=" + layoutX + ", layoutY=" + layoutY + ", managed=" + managed + ", opacity=" + opacity + ", pressed=" + pressed + ", parent=" + parent + ", visible=" + visible + '}'; } }
The Node is a base class that represents a scenegraph. It can have parent, which is looks like this:
Node is the base abstract class that represents one scenegraph node in the new Prism (iceberg).
Every Node can have a parent Node:
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package uk.co.xenonique.javafx; import java.util.*; /** * @author Peter Pilgrim */ public abstract class Parent extends Node { protected List<Node> children = new ArrayList<Node>(); protected boolean needsLayout; public Parent() { } public List<Node> getChildren() { return children; } protected Parent setChildren(List<Node> children) { this.children = children; return this; } ... @Override public String toString() { return "Parent{" + "children=" + children + ", needsLayout=" + needsLayout + '}'; } }
Parent and Node are abstract classes. Group is the simplest container type of Parent and it is a concrete type. A group represents a collection of scene graph nodes, which is not the same of a container. So here is the Java API version.
package uk.co.xenonique.javafx; import java.util.*; public class Group extends Parent { public Group() { } public List<Node> getContent() { return getChildren(); } public void setContent(List<Node> content) { this.setContent( content ); } ... }
Let us look at the Shape as a Java class, or at the very least the basics of it.
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package uk.co.xenonique.javafx; public abstract class Shape extends Node { protected Color fill = new Color( 0x999966); protected Color stroke = new Color(0xFF9900); protected float strokeWidth = 1.0F; public Shape() { super(); } public Color getFill() { return fill; } public Shape setFill(Color fill) { this.fill = fill; return this; } ... }
Finally, here is the outline of the remaining JavaFX for Java API classes that we need. Namely, they are the Rectangle, Stage and the Scene classes:
package uk.co.xenonique.javafx; /** * @author Peter Pilgrim */ public class Rectangle extends Shape { private float x; private float y; private float width; private float height; public Rectangle() { } public float getHeight() { return height; } public Rectangle setHeight(float height) { this.height = height; return this; } ... }
The Stage:
package uk.co.xenonique.javafx; public class Stage { private String title; private float width; private float height; private float x; private float y; private Bounds stageBounds; private Bounds screenBounds; private Scene scene; private boolean visible; private float opacity; public Stage() { } public float getWidth() { return width; } public Stage setWidth(float width) { this.width = width; return this; } public Scene getScene() { return scene; } public Stage setScene(Scene scene) { this.scene = scene; return this; } ... }
Finally, the Scene:
package uk.co.xenonique.javafx; import java.util.*; public class Scene extends Parent { public Scene() { } public List<Node> getContent() { return getChildren(); } public Scene setContent(Node content) { this.setContent( content ); return this; } ... }
So how on earth do we call this Java API from Scala? What could it look like? Here is a work-in-progress Domain Specific Language example based on my own attempts of modelling these Java API classes. So far it looks like this:
object TestDSL { def main(args: Array[String]) { val builder = new FXBuilder() import builder._ val s1 = stage("Demo") { s => val s2 = scene { s => group { g => rectangle() { n => n.setX(50) .setY(50) .setWidth(100) .setHeight(200) .setFill( new Color( 0xFFCC00 ) ) .setStrokeWidth(2.0F) .setStroke(new Color( 0xCC9900) ) } } } } s1.setVisible(true) } } object TestDSL { def main(args: Array[String]) { val builder = new FXBuilder() import builder._ val s1 = stage("Demo") { s => val s2 = scene { s => group { g => rectangle() { n => n.setX(50) .setY(50) .setWidth(100) .setHeight(200) .setFill( new Color( 0xFFCC00 ) ) .setStrokeWidth(2.0F) .setStroke(new Color( 0xCC9900) ) } } } } s1.setVisible(true) } }
Writing a Scala DSL is not easy to tell the truth. Having attend the Scala LiftOff USA after JavaOne 2010 I sought some advice, and the best advice I was given was to take things slowly. Build slowly and deliberately, the DSL is an internal one. Here are some observations:
- The Scala DSL at this stage is not replacement for JavaFX Script or Visage project. Scala does not support declarative instantiation of objects in 2.8.0-final.
- The closures in Groovy have an implicit “it” parameter that are passed to them, in this way Groovy Builder can hide an implicit context. See examples of code that use Groovy’s XML, Ant and Swing builders and you will see this. In Scala we cannot simulate this. Therefore note the explicity closure parameter inside each of the closures.
- Each closure parameter represents the JavaFX Java object in question. Hence we can chain the operations together in the form n.setX(10).setY(10).setWidth(100).setHeight(100) etc
- Due to way Scala is calling Java classes it is not possible to support in-fix operations directly. So the following does not cut it: n setX 10 setY 10 setWidth 100 setHeight 100.
- The advantage of using an FXBuilder and an explicit closure parameter should make binding using the official Java APIs easier rather than harder. If you need to call a specific API routine your closure has the object instance.
- The FXBuilder DSL is a lightweight wrapper around the JavaFX for Java API.
- The FXBuilder also knows how to add a Node to Parent. It would have to be extended to handle collections of nodes, namely List[Node].
Of course, Scala is a statically typed language and there is great type safety in there. Getting the code with the mock JavaFX API classes to compile required some enduration as my knowledge of Scala does not quite match my long Java experience just yet. It is the first cut and writing any DSL is a long road and an investment. The equivalent JavaFX Script/ Visage looks this:
def s1:Stage = Stage { scene: Scene { content: Group { content: Rectangle { x: 50 y: 50 width: 200 height: 200 fill: Color.web("#FFCC00") strokeWidth: 2.0 stroke: Color.web("#CC9900") } } }
All the fun of the fair. This is Peter Pilgrim. Out.
PS: I am all ears for your opinion by the way.
From http://www.xenonique.co.uk/blog/?p=55
Opinions expressed by DZone contributors are their own.
Comments