Monday, February 15, 2010

JavaFx: Integrating with Java

JavaFx applications run on the JVM, which means that they must use class file formats that are compatible with those produced by the Java compiler. Which in turn means that JavaFx code should be able to call Java code and vice-versa. And, in fact, JavaFx to Java integration is easy:

import java.lang.Math;

var a = 12;
var b = 13;
var c = Math.min(a, b);
println(c);

var d = "this is a test";
var e = d.replaceAll("a test", "an example");
println(e);

Because of type inference, variables a and b are known to be integers, which are represented internally as int (not, as you may expect, as Integer — although that's no doubt an implementation detail and subject to change). This means that they can be passed directly to the Java method Math.min(). Similarly, variable d is known to be a java.lang.String, so you can invoke instance methods defined by that class.

Calling from Java into JavaFx is a little more difficult. Executing a script from a Java method is not practical: there are a number of methods that you must invoke, and the specific names and call order is undocumented. Script functions are compiled as public static methods, so you could theoretically import the script class and call these directory. However, any non-trivial script function would rely on script variables, which brings us back to the issue of setting up context.

JavaFx classes, however, are compiled as public nested (static inner) classes of their defining script. Which means you can instantiate a JavaFx class from within Java code. However, all of the instance variables will be set to default values — and the setter method names are, of course, implementation details and subject to change. Worse, NetBeans doesn't recognize that the compiled class exists, and flags your Java code as an error; you have compile manually (I didn't try Eclipse).

That leaves instantiating a JavaFx class from within JavaFx code, and passing it to a Java class for use. This is surprisingly easy, although convoluted. You first create a Java interface:

public interface CallbackIntf
{
    public void callback(String param);
}

A JavaFx class can then implement that interface:

class MyCallee
extends CallbackIntf
{
    override function callback(param : String)
    {
        println("callback invoked: {param}");
    }
}

Next, you instantiate your Java object from within the JavaFx script (in this example, Callback2), and then you can call a method on that object. Incidentally, note that we use new to instantiate the Java object, and can pass arguments into its constructor.

var caller = new Callback2("test");
var callee = new MyCallee;
caller.makeCallback(callee as CallbackIntf);

Convoluted, yes, but it offers promise: many framework objects (eg, Servlet) are specified as interfaces. If you're writing apps that could benefit from JavaFx language features (such as data binding), it's theoretically possible to do so and deploy into a Java framework. In practice, however, I don't think it will work. Threading is one reason, and that's the subject of my next post.

No comments: