graniteds.orgCommunity Documentation

Chapter 5. JavaFX Code Generator

5.1. Overview
5.2. Generated JavaFX Classes
5.3. Java Classes and Corresponding Templates
5.4. Eclipse Plugin
5.5. Ant Task
5.6. Maven Plugin (Flexmojos)
5.7. Template Language

One of the main interests of using AMF remoting is that is can maintain a strongly typed bindable JavaFX data model in the client application. However that implies that you have to write a specific JavaFX class for each Java class that will be serialized. Writing and maintaining these JavaFX beans is tedious and a source of many errors. In order to solve this problem and accelerate the development of JavaFX/Java EE applications, GraniteDS comes with an code generator that writes JavaFX beans for all Java beans.

Additionally this generator specifically supports the externalization mechanism of GraniteDS and is able to generate corresponding JavaFX classes for externalized Java beans (typically JPA/Hibernate entities) with specific templates.

Finally this generator is able to write typesafe client proxies for exposed remote services. Compared to the RemoteService API, this can greatly help development by bringing auto-completion and improved type-safety when using remote services.

Gfx may also replicate validation annotations in order to use the client side validation framework (see Bean Validation (JSR-303)).

The generator (named GFX) is implemented as an Ant task. This Ant task is packaged as an Eclipse 3.2+ Ant plugin but may also be used outside of Eclipse for command line Ant calls. It can also be used with Maven by means of the Flexmojos Maven plugin.

A common problem with code generators is the potential loss of manual modifications made in generated files. A generated file must be either generated once and only once, allowing for safe manual modifications, but it will not be able to reflect the modifications made in its model (JavaBeans), or regenerated each time its model has been changed, thus preventing safe manual modifications.

Gfx uses the principle of "Base" and customizable inherited classes that let you add methods to generated classes without facing the risk of losing them when a new generation process is executed. For example, here are the two files generated for a given Java entity bean:

Welcome.java



package org.test;
import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Welcome implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id @GeneratedValue
    private Integer id;
    
    @Basic
    private String name;
    public Welcome() {
    }
    public Welcome(String name) {
        this.name = name;
    }
    
    public Integer getId() {
        return id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
        

Welcome.java

/**
 * Generated by Gas3 v2.3.0 (Granite Data Services).
 *
 * NOTE: this file is only generated if it does not exist. You may safely put
 * your custom code here.
 */

package org.test.client;

@JavaFXObject
@RemoteClass("org.test.Welcome")
public class Welcome extends WelcomeBase {
}
        

WelcomeBase.java



/**
 * Generated by Gas3 v2.3.0 (Granite Data Services).
 *
 * WARNING: DO NOT CHANGE THIS FILE. IT MAY BE OVERWRITTEN EACH TIME YOU USE
 * THE GENERATOR. INSTEAD, EDIT THE INHERITED CLASS (Welcome.as).
 */
package org.test.client;
@JavaFXObject
public class WelcomeBase implements IExternalizable {
    ...
}
        

The recommendations for manual editing are explicit in the header comments of each generated classes: while the "Base" class may be regenerated at any time, keeping it sync with its Java model class, the inherited one is only generated when it does not exist and you may safely add custom methods into it.

This two files generation principle is used for all generated classes except interface and enum: these classes are generated without any "Base" class and overwritten each time you have modified their Java counterparts.

Here are the details for (re)generation conditions:

Note that for Java classes, relevant timestamp is the last modified time of the .class file, not the .java file.

TemplatesConditions for (re)generation
Dual templates (base + inherited) The inherited JavaFX class is generated only once if it does not exist. The JavaFX base one is generated if it does not exist or if its timestamp (last modified time) is less than the Java class one
Single template (enums or interfaces) Like the base condition above, the JavaFX class is (re)generated if it does not exist or if its timestamp is less than the Java class one

Here is the summary of templates used by the generator depending on the kind of Java class it encounters:

Type of Java ClassTemplateBase Template
Standard Java beansbean.gspbeanBase.gsp
JPA entities: all classes annotated with @Entity and @MappedSuperclassentity.gspentityBase.gsp
Java enumsenum.gsp(none)
Java interfacesinterface.gsp(none)
Java services: all classes annotated with @RemoteDestinationremote.gspremoteBase.gsp
Java events (CDI): all classes annotated with @TideEventbean.gspbeanBase.gsp

Note that all these templates are bundled in the granite-generator.jar archive, in the org.granite.generator.javafx.template package and accessible as resources via the class loader.

TODO (see doc for Flex plugin).

JavaFX classes can also be generated with the Gas3 plugin for Flex. You just need to define a specific configuration to generate JavaFX classes :

Installation in Eclipse

Download org.granite.builder_***.jar, and drop it in your Eclipse plugins directory (remove any older version and restart Eclipse). The Add GraniteDS Nature option should now be available if you right-click on your Java project and the gas3 Ant task should be ready to use in your build.xml file under Eclipse.

Standalone Installation

Download org.granite.builder_***.jar and unzip it somewhere (create a new directory, this jar doesn't contain a root folder). Move the lib directory somewhere else (say gas3libs at the root of you harddrive). In your build.xml, you must declare the Gfx ant task as follows:



<taskdef name="gas3" classname="org.granite.generator.javafx.AntJavaFXTask"/>
       

To launch a build process with Gfx targets, you should go to your Java source root directory and type something like:

$ ant -lib /gfxlibs -f build.xml {target}
...
       

Just replace {target} with a valid target name and make sure Ant is correctly set up: set ANT_HOME variable and put <ANT_HOME>/bin in your PATH environment variable.

Basic Usage

After installation, you may use the Gas3 Ant task in any target of an Ant build file. A working example of Gas3 usage is available in the examples/graniteds_ejb3 sample application. For example:



<target name="generate.fx">
    <gfx outputdir="java">
        <classpath>
            <pathelement location="classes"/>
        </classpath>
        <fileset dir="classes">
            <include name="com/myapp/entity/**/*.class"/>
        </fileset>
    </gas3>
</target>
        

As you can notice, Gfx generates JavaFX beans from Java compiled classes. You may use multiple Ant filesets in order to specify for which JPA classes you want to generate JavaFX beans. The classpath node is used for fileset class loading, and you may reference extra jars or classes needed by your beans class loading.

The outputdir attribute lets you instruct Gfx in which directory JavaFX beans will be generated (e.g., ./java). This path is relative to your current project directory and Gfx will create subdirectories for packages. JavaFX beans will by default have the same package hierarchy as Java classes, with the same subdirectories as well. This may not be very convenient, so it is recommended that you use a package translation definition (see below package translators).

For each JPA entity (say com.myapp.entity.MyEntity), Gfx will generate two JavaFX beans:

  • org.entity.MyEntityBase.java: This bean mainly contains fields, getters, setters, and extra methods. This file is generated if it does not exist or if it is outdated.

  • org.entity.MyEntity.java: This bean inherits from the "Base" one and is only generated if it does not exist.

While you should not modify the "Base" file, since your modifications may be lost after another generation process, you may safely add your code to the inherited bean.

You can also use Ant zipfilesets if you want to generate JavaFX classes from an existing jar. Note that the jar must be in the classpath:



<target name="generate.fx">
    <gfx outputdir="java">
        <classpath>
            <pathelement location="lib/myclasses.jar"/>
        </classpath>
        <zipfileset src="lib/myclasses.jar">
            <include name="com/myapp/entity/**/*.class"/>
        </zipfileset>
    </gas3>
</target>
        

Packages Translations

You may tell Gfx to generate client classes with a different package and directory structure than the corresponding Java server classes. This is even highly recommended to avoid classpath conflicts or ambiguous autocompletions in the IDE.



<gfx ...>
    <classpath .../>
    <fileset .../>

    <translator
        java="path.to.my.java.class"
        client="path.to.my.client.class" />
    <translator
        java="path.to.my.java.class.special"
        client="otherpath.to.my.client.class.special" />
  ...
</gfx>
        

Gfx uses these translators with a "best match" principle; all Java classes within the path.to.my.java.class package, and subpackages as well, will be translated to path.to.my.client.class, while path.to.my.java.class.special will use a specific translation (otherpath.to.my.client.class.special).

Groovy Templates

Gfx generation relies on Groovy templates. You may plug your own templates in by using one of the advanced options attributes below. For example, you could add a entitytemplate="/absolute/path/to/my/groovy/entityTemplate.gsp" attribute to the gfx node. You can also specify paths to your custom templates relative to the current Ant project basedir directory. If you want to see the Groovy code of the default templates, just unpack granite-generator.jar in the lib directory of the plugin, and look for org/granite/generator/template/*[Base].gsp files.

Advanced Options (Gfx XML Attributes)

Here is the complete list of Gfx node attributes:

For example:



<target name="generate.fx">
    <gfx
        outputdir="java"
        baseoutputdir="base_java"
        uid="myUidFieldName"
        entitytemplate="/myEntityTemplate.gsp"
        entitybasetemplate="/myEntityBaseTemplate.gsp"
        interfacetemplate="/myInterfaceTemplate.gsp"
        beantemplate="/myBeanTemplate.gsp"
        beanbasetemplate="/myBeanBaseTemplate.gsp"
        enumtemplate="/myEnumTemplate.gsp"
        remotetemplate="/myRemoteTemplate.gsp"
        remotebasetemplate="/myRemoteBaseTemplate.gsp"
        tide="true"
        clienttypefactory="path.to.MyCustomTypeFactory"
        entityfactory="path.to.MyEntityFactory"
        remotedestinationfactory="path.to.MyRDFactory"
        transformer="path.to.MyTransformer"
        externalizelong="true"
        externalizebiginteger="true"
        externalizebigdecimal="true">
        <classpath>
            <pathelement location="classes"/>
        </classpath>
        <fileset dir="classes">
            <include name="test/granite/ejb3/entity/**/*.class"/>
        </fileset>
    </gas3>
</target>
        

Note that when using a custom clienttypefactory, entityfactory, remotedestinationfactory or transformer attribute, you must configure the classpath in order to make your custom classes available to the Gfx engine; either use the classpath attribute in the taskdef declaration or in the gfx call.

The Gfx generator is used as the default code generation tool in the Flexmojos plugin. To use it, you need to add the following part to your maven POM :



<build>
    ...
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.sonatype.flexmojos</groupId>
                <artifactId>flexmojos-maven-plugin</artifactId>
                <version>${flexmojos.version}</version>
            </plugin>
        </plugins>
    </pluginManagement>
    
    <plugins>
        <plugin>
            <groupId>org.sonatype.flexmojos</groupId>
            <artifactId>flexmojos-maven-plugin</artifactId>
            <version>${flexmojos.version}</version>
            <extensions>true</extensions>
            <executions>
                <execution>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                    <configuration>
                        <generatorToUse>graniteds23</generatorToUse>
                        <baseOutputDirectory>${project.build.directory}/generated-sources</baseOutputDirectory>
                        <outputDirectory>${basedir}/src/main/java</outputDirectory>
                        <translators>
                            <translator>com.myapp.server=com.myapp.client</translator>
                        </translators>
                        <extraOptions>
                            <tide>true</tide>
                            <uid>uid</uid>
                            <transformer>org.granite.generator.javafx.JavaFXGroovyTransformer</transformer>
                            <as3typefactory>org.granite.generator.javafx.DefaultJavaFXTypeFactory</as3typefactory>
                            <entityFactory>org.granite.generator.as3.BVEntityFactory</entityFactory>
                            <outputEnumToBaseOutputDirectory>false</outputEnumToBaseOutputDirectory>
                        </extraOptions>
                        <includeJavaClasses>
                            <include>${package}.entities.**</include>
                            <include>${package}.services.*Service</include>
                        </includeJavaClasses>
                        <templates>
                            <base-bean-template>classpath:org/granite/generator/javafx/template/tideBeanBase.gsp</base-bean-template>
                            <bean-template>classpath:org/granite/generator/javafx/template/bean.gsp</bean-template>
                            <base-entity-template>classpath:org/granite/generator/javafx/template/tideEntityBase.gsp</base-entity-template>
                            <entity-template>classpath:org/granite/generator/javafx/template/entity.gsp</entity-template>
                            <base-remote-template>classpath:org/granite/generator/javafx/template/tideRemoteBase.gsp</base-remote-template>
                            <remote-template>classpath:org/granite/generator/javafx/template/tideRemote.gsp</remote-template>
                            <enum-template>classpath:org/granite/generator/javafx/template/enum.gsp</enum-template>
                        </templates>
                    </configuration>
                </execution>
            </executions>
            <dependencies>
                <dependency>
                    <groupId>org.hibernate.javax.persistence</groupId>
                    <artifactId>hibernate-jpa-2.0-api</artifactId>
                    <version>1.0.1.Final</version>
                </dependency> 
                <dependency>
                    <groupId>javax.validation</groupId>
                    <artifactId>validation-api</artifactId>
                    <version>1.0.0.GA</version>
                </dependency> 
                <dependency>
                    <groupId>javax.jdo</groupId>
                    <artifactId>jdo2-api</artifactId>
                    <version>2.3-eb</version>
                </dependency>
                <dependency>
                    <groupId>org.codehaus.groovy</groupId>
                    <artifactId>groovy</artifactId>
                    <version>1.6.0</version>
                </dependency> 
                <dependency>
                    <groupId>antlr</groupId>
                    <artifactId>antlr</artifactId>
                    <version>2.7.7</version>
                </dependency>     
                <dependency>
                    <groupId>asm</groupId>
                    <artifactId>asm</artifactId>
                    <version>2.2.3</version>
                </dependency> 
                <dependency>
                    <groupId>com.thoughtworks.xstream</groupId>
                    <artifactId>xstream</artifactId>
                    <version>1.2.2</version>
                </dependency> 
                <dependency>
                  <groupId>org.sonatype.flexmojos</groupId>
                  <artifactId>flexmojos-generator-graniteds-2.3.0</artifactId>
                  <version>${flexmojos.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.graniteds</groupId>
                    <artifactId>granite-core</artifactId>
                    <version>${graniteds.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.graniteds</groupId>
                    <artifactId>granite-generator-share</artifactId>
                    <version>${graniteds.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.graniteds</groupId>
                    <artifactId>granite-generator</artifactId>
                    <version>${graniteds.version}</version>
                </dependency>
            </dependencies>
        </plugin>
        ...
    </plugins>
    ...
</build>
       

TODO: see documentation for Gas3 ActionScript 3 generator.