Convert FXML to Java as part of the build


FXML is a declarative way to define you JavaFX UIs, you can edit it by hand e.g. useing e(fx)clipse, use a DSL like FXGraph or use a WYSIWYG-Tool like SceneBuilder.

At runtime a class named FXMLLoader takes the FXML and creates a SceneGraph out of it using reflection. Let’s look at a small sample.

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Button?>

<BorderPane xmlns:fx="http://javafx.com/fxml">
  <center>
    <Button text="Hello World"></Button>
  </center>
</BorderPane>

Loading in Java:

BorderPane p = FXMLLoader.load(getClass().getClassLoader().getResource("Sample.fxml"));

Simple & easy BUT it is slow. On desktop system the slowness might not bother you but on constrainted devices like Raspberry Pi or smartphone reflection it will.

So the idea I had already since some time [Bug] is to move translation from runtime to build time so that one can simply add an instruction to the build script and FXML-Files are translated to Java-Files and compiled by the Java compiler.

While not yet complete I can now share the first version which does at least supports the most basic stuff. To build my JavaFX app which uses FXML I can now use an ant-file like this:

<project default="test">
  <path id="fxcompile">
    <filelist>
      <file name="build/classes"/>
      <file name="build-libs/org.eclipse.fx.fxml.compiler_0.9.0-SNAPSHOT.jar"/>
    </filelist>
  </path>
	
  <path id="buildpath">
    <filelist>
      <file name="build/classes"/>
      <file name="libs/org.eclipse.fx.core_0.9.0.201308281625.jar"/>
    </filelist>
  </path>
	
 <target name="test">
   <delete dir="build" />
   <mkdir dir="build/classes"/>
		
   <taskdef name="fxml-compiler" classpathref="fxcompile" classname="org.eclipse.fx.ide.fxml.compiler.ant.FXMLCompilerTask" />
		
   <javac srcdir="src" destdir="build/classes" classpathref="buildpath"></javac>
		
   <fxml-compiler sourcedir="src" destdir="build/gen-src"/>
   <javac srcdir="build/gen-src" destdir="build/classes" classpathref="buildpath"></javac>
  </target>
</project>

The important line is <fxml-compiler… which instructs the build to translate all FXML-Files found below src into .java-Files and place them into build/gen-src.

Generated Java-File:

import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;
import javafx.scene.control.Button;

import java.util.Map;
import java.net.URL;
import java.util.ResourceBundle;

import org.eclipse.fx.core.fxml.FXMLDocument;

import java.util.HashMap;

@SuppressWarnings("all")
public class Sample extends FXMLDocument<BorderPane> {
  private Map<String,Object> namespaceMap = new HashMap<>();
  public Object getController() {
    return null;
  }
	
  public BorderPane load(URL location, ResourceBundle resourceBundle) {
    BorderPane root = new BorderPane();
    {
      Button e_1 = new Button();
      e_1.setText("Hello World");
      root.setCenter(e_1);
    }
    return root;
  }
}

In the original code the only change I have to make is switch from FXMLLoader to a smarter class which first checks if there is a .class-File with the same name and create an instance of it.

ExtendedFXMLLoader loader = new ExtendedFXMLLoader();
loader.load(getClass().getClassLoader(),"Sample.fxml");

You can play with it yourself. I’ve uploaded a sample project to http://downloads.efxclipse.org/fxml-compiler.zip, give it a try on your own FXML-Files and if things fail file bugs against e(fx)clipse.

40 thoughts on “Convert FXML to Java as part of the build

  1. Sébastien Bordes August 30, 2013 / 5:32 am

    Great job this is the so important feaure that was missing for FXML ! I hope that FXML will leverage MXML features.
    What about creating a maven plugin too ?

    • Tom Schindl August 30, 2013 / 8:56 am

      I don’t know maven, file a bug and contribute it😉

  2. Hayden Jones September 3, 2013 / 6:54 pm

    Is it possible to download this tool by itself as a jar file?

    One thing I don’t like about fxml is that it needs all-permission to do the binding. Does generating the java class file prevent the need of the reflection API for the binding of the fxml to the controller?

    Thanks,

    • Tom Schindl September 3, 2013 / 7:02 pm

      The sample project holds the tool jar. On reflection if fields and methods in your controller are public no reflection is used.

      • haydenpjones September 3, 2013 / 7:13 pm

        Tx, I’ll have a look.

      • Tom Schindl September 3, 2013 / 7:17 pm

        Please note this alpha code so not all fxml files will translate and before i forget there’s still one reflection call needed

      • haydenpjones September 5, 2013 / 7:20 pm

        I’ve given it a try…

        I am trying to run the FXGraphCompiler directly but get an

        org.xml.sax.SAXException: javafx.geometry.Insets.getId();
        java.lang.NoSuchMethodException…

        Looking forward to see the progress you make on this project.

      • Tom Schindl September 5, 2013 / 7:23 pm

        Ok so your inset has an id attribute? Can you show me the part of the fxml where the inset is defined?

      • Hayden Jones September 5, 2013 / 10:22 pm

        HiTom,

        I deleted the id on the Insets in the fxml.

        The next problem I got was having a combobox which had three default items which the parse could not handle. I tried removing the fxcollections underneath but then I got another error which said something about parameter count.

        Maybe I can send you the fxml?

      • Tom Schindl September 5, 2013 / 10:24 pm

        Sure tom dot schindl at bestsolution dot at

      • Hayden Jones September 6, 2013 / 1:00 pm

        I have sent the file, if you do not receive it let me know.

        Thanks.

      • Tom Schindl September 6, 2013 / 1:01 pm

        Received it but did not yet have time to take a look

  3. TPC September 8, 2013 / 8:41 pm

    Looks great! A maven plugin would be great, if you need help let me know.

    • Tom Schindl September 8, 2013 / 8:42 pm

      File a bug and contribute a patch🙂

  4. ssawan786 October 14, 2013 / 5:51 pm

    where can i get the latest fxml-compiler.zip or its related files. which files are needed from efxclipse nitely build area to update your distribution?

  5. Vimal raj October 24, 2013 / 6:02 am

    I got this exception while trying to bulild.
    JRE : 1.7.40
    Eclipse Kepler.

    Buildfile: D:\Jack\Workspaces\JavaFX\CompilerTest\build.xml
    test:
    [delete] Deleting directory D:\Jack\Workspaces\JavaFX\CompilerTest\build
    [mkdir] Created dir: D:\Jack\Workspaces\JavaFX\CompilerTest\build\classes
    [javac] D:\Jack\Workspaces\JavaFX\CompilerTest\build.xml:25: warning: ‘includeantruntime’ was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Compiling 5 source files to D:\Jack\Workspaces\JavaFX\CompilerTest\build\classes
    [fxml-compiler] Compiling D:\Jack\Workspaces\JavaFX\CompilerTest\src\demo\Login.fxml

    BUILD FAILED
    D:\Jack\Workspaces\JavaFX\CompilerTest\build.xml:27: java.lang.NoSuchMethodError: java.lang.reflect.Constructor.getParameterCount()I

    Total time: 5 seconds

      • Lars Briem January 14, 2014 / 2:45 pm

        Hi Tom,

        I got the same exception as Vimal raj and fixed it with the mentioned nightly. But I get another exception:
        BUILD FAILED
        C:\Workspaces\CompilerTest\build.xml:24: java.lang.NoSuchMethodError: java.lang.reflect.Type.getTypeName()Ljava/lang/String;

      • Tom Schindl January 14, 2014 / 2:48 pm

        This is when running on java7 – i used jdk8 APIs

      • Lars Briem January 14, 2014 / 3:22 pm

        I’m not able to update to Java8, because of our project requirements.
        Can it be fixed for java7?

        Can I use your compiler with jdk8 and compile the generated javafx classes with java7?

      • Tom Schindl January 14, 2014 / 3:39 pm

        Yes please file a bug – but just to make sure you’d only need it for compile time not runtime!

  6. Sven February 11, 2014 / 4:10 pm

    Hi there
    This is great work! However, the link to the nightly builds doesn’t work. Do you have another link?

  7. Gernot October 2, 2014 / 6:27 am

    Hi! This sounds very promising, as I am also about to run into performance issues loading FXMLs. Is this project still alive? The download link is dead unfortunately.

    • Tom Schindl October 2, 2014 / 6:38 am

      Yeah blog links are often going stale, the project is alive and we are planing to improve it in the next release. I’m currently unable to give you the URL but the tool is built as part of our nightly CI job

      • Gernot October 23, 2014 / 5:55 am

        thanks a lot, also for the quick reply to my filed bugs🙂 keep up the good work!

  8. Johny HP June 8, 2015 / 8:46 pm

    Why did this useful tool dissapeared?

    • Tom Schindl June 9, 2015 / 8:07 am

      It did not disappear but is still available from our eclipse download page

      • Johny HP June 28, 2015 / 12:55 pm

        Tom, first congrats for the great job you have done. However, the latest version you released(1.2.0) does not include the org.eclipse.fx, and moreover the ” org.eclipse.fx.ide.fxml.compiler.ant.FXMLCompilerTask, therefore I cannot compile. Have you released any version that supports “fx:include”?

      • Tom Schindl July 7, 2015 / 6:10 pm

        No we have not worked on fx:include IIRC

  9. Christian August 11, 2015 / 3:23 pm

    Hi Tom,

    I’ve got the same question like Johny HP. The FXMLCompiler seems to be absent from the org.eclipse.fx.fxml.compiler_1.2.0-SNAPSHOT.jar artifact. Is that an ommission or by design – maybe the package moved to some other artifact? If so into which one?

    Also, referring to:
    http://download.eclipse.org/efxclipse/?d

    … in folder:
    http://download.eclipse.org/efxclipse/compiler-released/1.1.0/?d

    … the artifact org.eclipse.fx.fxml.compiler_1.1.0-SNAPSHOT.jar does indeed contain the compiler package along with the ant task.

    So is 1.1.0 the latest usable Version or alternatively would it be worthwile to git clone https://git.eclipse.org/r/efxclipse/org.eclipse.efxclipse and build it myself? There the classes in question seem to be in https://git.eclipse.org/c/efxclipse/org.eclipse.efxclipse.git/tree/bundles/tooling/org.eclipse.fx.ide.fxml.compiler/src/org/eclipse/fx/ide/fxml/compiler.

    Best,
    Christian

    • Tom Schindl August 25, 2015 / 9:35 am

      Yes you currently need to checkout the stuff from the repo. We wanted to provide a standalone app with 2.1.0 but did not have enough resources

  10. TT September 7, 2015 / 10:06 am

    I tried running this in Eclipse with Ant using the FXML compiler from the version 2.1.0 e(fx)clipse plugin on Eclipse Mars and JDK 1.8.0_60, but I always get the following error:

    1) Error injecting constructor, org.eclipse.emf.common.util.WrappedException: java.lang.ClassCastException: org.eclipse.xtext.resource.XtextPlatformResourceURIHandler cannot be cast to org.eclipse.emf.ecore.xmi.XMLResource$URIHandler

    Parts of the stacktrace:
    Caused by: org.eclipse.emf.common.util.WrappedException: java.lang.ClassCastException: org.eclipse.xtext.resource.XtextPlatformResourceURIHandler cannot be cast to org.eclipse.emf.ecore.xmi.XMLResource$URIHandler
    at org.eclipse.xtext.parser.BaseEPackageAccess.loadResource(BaseEPackageAccess.java:57)
    […]
    at org.eclipse.fx.ide.fxgraph.FXGraphStandaloneSetupGenerated.createInjectorAndDoEMFRegistration(FXGraphStandaloneSetupGenerated.java:21)
    at org.eclipse.fx.ide.fxml.compiler.ant.CompilerTask.execute(CompilerTask.java:75)
    […]

    The last lines are the first time org.eclipse.fx appears in the trace.

    Any ideas? Is this to do with your plugin or is it an incompatibility between Eclipse plugins? Would you like me to email you the full stacktrace somewhere?

    • Tom Schindl September 7, 2015 / 10:14 am

      We did not manage to update the fxml-compiler in 2.1.0😦 – You can file a bug so that we fix this and svg2 fxml converter in 2.2.0

  11. Marcel October 6, 2015 / 6:49 am

    There is a bug when the FXML has a tag inside.

    Example FXML part:

    ..
    The Exception is:
    org.xml.sax.SAXException: javafx.scene.layout.AnchorPane.getDefine()
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1261) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:649) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(SAXParserImpl.java:333) ~[na:1.8.0_20]
    at javax.xml.parsers.SAXParser.parse(SAXParser.java:195) ~[na:1.8.0_20]
    at org.eclipse.fx.ide.fxml.compiler.FXGraphCompiler.compileFXML(FXGraphCompiler.java:98) ~[compiler-0.9.0.jar:na]
    at com.proemion.linde.fdatool.FXMLToJava$1.visitFile(FXMLToJava.java:58) [classes/:na]
    at com.proemion.linde.fdatool.FXMLToJava$1.visitFile(FXMLToJava.java:1) [classes/:na]
    at java.nio.file.Files.walkFileTree(Files.java:2670) [na:1.8.0_20]
    at java.nio.file.Files.walkFileTree(Files.java:2742) [na:1.8.0_20]
    at com.proemion.linde.fdatool.FXMLToJava.main(FXMLToJava.java:47) [classes/:na]
    Caused by: java.lang.NoSuchMethodException: javafx.scene.layout.AnchorPane.getDefine()
    at java.lang.Class.getMethod(Class.java:1773) ~[na:1.8.0_20]
    at org.eclipse.fx.ide.fxml.compiler.ReflectionHelper.getValueType(ReflectionHelper.java:404) ~[compiler-0.9.0.jar:na]
    at org.eclipse.fx.ide.fxml.compiler.FXMLSaxHandler.startElement(FXMLSaxHandler.java:170) ~[compiler-0.9.0.jar:na]
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:509) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:379) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2786) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:117) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) ~[na:1.8.0_20]
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213) ~[na:1.8.0_20]
    … 9 common frames omitted

    • Tom Schindl October 6, 2015 / 7:32 pm

      File a bug with a reproduceable case

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s