graniteds.orgCommunity Documentation
One of the main interests of using AMF remoting is that is can maintain a strongly typed data model in the Flex application. However that implies that you have to write an AS3 class for each Java class that will be serialized. Writing and maintaining these ActionScript 3 beans is tedious and a source of many errors. In order to solve this problem and accelerate the development of Flex/Java EE applications, GraniteDS comes with an ActionScript3 code generator that writes AS3 beans for all Java beans.
Additionally this generator specifically supports the externalization mechanism of GraniteDS and is able to generate corresponding AS3 classes for externalized Java beans (typically JPA/Hibernate entities) with specific templates.
Finally this ActionScript 3 generator is able to write AS3 typed client proxies for exposed remote services.
Compared to the usual Flex RemoteObject, this can greatly help development by bringing auto-completion and improved type-safety in Flex
when using remote services.
Starting with GraniteDS 2.2, Gas3 may also replicate validation annotations in order to use the Flex side validation framework (see Bean Validation (JSR-303))
and may also be configured to generate Long, BigInteger and BigDecimal variable for their Java equivalents (see Big Number Implementations).
The generator (named GAS3) is implemented as an Eclipse plugin and 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 is also included in the Flexmojos Maven plugin.
The next sections introduce both the Eclipse plugin and Ant task configurations and usages. You may also have a look at the Eclipse Plugins Installation section and at the Hello World revisited tutorial for a sample Eclipse Builder usage.
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.
Gas3 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.as
/**
* Generated by Gas3 v2.2.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 {
[Bindable]
[RemoteClass(alias="org.test.Welcome")]
public class Welcome extends WelcomeBase {
}
}
WelcomeBase.as
/**
* Generated by Gas3 v2.2.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 {
import flash.utils.IDataInput;
import flash.utils.IDataOutput;
import flash.utils.IExternalizable;
import org.granite.collections.IPersistentCollection;
import org.granite.meta;
use namespace meta;
[Bindable]
public class WelcomeBase implements IExternalizable {
private var __initialized:Boolean = true;
private var __detachedState:String = null;
private var _id:Number;
private var _name:String;
meta function isInitialized(name:String = null):Boolean {
if (!name)
return __initialized;
var property:* = this[name];
return (
(!(property is Welcome) || (property as Welcome).meta::isInitialized()) &&
(!(property is IPersistentCollection) ||
(property as IPersistentCollection).isInitialized())
);
}
public function get id():Number {
return _id;
}
public function set name(value:String):void {
_name = value;
}
public function get name():String {
return _name;
}
public function readExternal(input:IDataInput):void {
__initialized = input.readObject() as Boolean;
__detachedState = input.readObject() as String;
if (meta::isInitialized()) {
_id = function(o:*):Number {
return (o is Number ? o as Number : Number.NaN) } (input.readObject());
_name = input.readObject() as String;
}
else {
_id = function(o:*):Number {
return (o is Number ? o as Number : Number.NaN) } (input.readObject());
}
}
public function writeExternal(output:IDataOutput):void {
output.writeObject(__initialized);
output.writeObject(__detachedState);
if (meta::isInitialized()) {
output.writeObject(_id);
output.writeObject(_name);
}
else {
output.writeObject(_id);
}
}
}
}
The recommendations for manual editing are explicit in the header comments of each AS3 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.
Note: Do not modify manually generated ActionScript3 interface or enum classes !
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.
| Templates | Conditions for (re)generation |
|---|---|
| Dual templates (base + inherited) | The inherited AS3 class is generated only once if it does not exist. The AS3 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 AS3 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 Class | Template | Base Template |
|---|---|---|
| Standard Java beans | bean.gsp | beanBase.gsp |
JPA entities: all classes annotated with @Entity and @MappedSuperclass | entity.gsp | entityBase.gsp |
| Java enums | enum.gsp | (none) |
| Java interfaces | interface.gsp | (none) |
Java services: all classes annotated with @RemoteDestination | remote.gsp | remoteBase.gsp |
Java events (CDI): all classes annotated with @TideEvent | bean.gsp | beanBase.gsp |
Note that all these templates are bundled in the granite-generator.jar archive, in the org.granite.generator.template package and accessible as resources via the class loader.
Gas3 does not support inner classes except of enum type. You must declare your classes in separated source files if you want them to be correctly handled by the generator.
Gas3 never deletes any file. However, when you remove or rename a Java class which is in the scope of the generator,
the corresponding AS3 classes are renamed to <ClassName>[Base].as.<System Current Millis>.hid.
This can lead to helpful Flex compilation errors if these classes where used anywhere else in your Flex source code.
Due to the absence of abstract classes in ActionScript 3, some inheritance models cannot be generated correctly.
Download org.granite.builder_***.jar and drop it in your Eclipse plugins directory,
making sure to remove any older versions. Then, restart Eclipse.
The Add GraniteDS Nature is available for any open Java project.
When you want to use the builder with your Java project, right-click on the project in your Eclipse package explorer
and select Add GraniteDS Nature:
This action should launch a configuration wizard, whose first step is to select Java source folders
for which you want code generation (ie. ActionScript 3 beans that mirror your JavaBeans):
You may select as many Java source folders as you want and configure specific filters and output directories for each of them.
Just select one of the Included, Excluded, or Output subnodes
and click on the Edit button.
For inclusion/exclusion patterns, the syntax is similar to the Ant include/exclude ones in fileset and the following rules apply:
In the example, **/*Service*.java will match any Java class which contains the Service string in its name and which
is in any subdirectory of the selected source folder.
Inclusion patterns let you specify arbitrary parameters which will be passed as a Map<String, String> to the concerned template (ie. the one
which is handling the kind of Java file which matches the include pattern). For example, you can specify an include pattern as follow:
**/*Service*.java[param1=value1,param2=value2]. In the template, for each file matching the **/*Service*.java
pattern, you will then have access to a specific variable named fAttributes, a map containing two keys "param1" and
"param2", bound to their respective values "value1" and "values2".
Note that this parameters feature is only available for the Eclipse builder (you can't use it with the Ant/Maven task).
For each selected Java source folder you may also configure specific output directories:
Output Directory: A directory relative to your project directory where generated ActionsScript3 classes are put.
Base Output Directory: An optional directory relative to your project directory where so-called "Base"
generated ActionsScript 3 classes are put. If left empty, the output directory above is used for both "Base" and inherited
ActionScript 3 classes. See here for this distinction.
The next step in the wizard allows you to configure Java project dependencies. This is required when your Java classes make references to
other classes declared in other Java projects. Clicking on the Add project button will open a dialog that lists all other
open Java projects which have the GraniteDS nature:
The next step is classpath configuration. If you do not use any custom classes in the Options panel
you do not need to change anything here since the classpath is automatically configured with your current selected source folders.
In the following picture, the helloworld/bin directory, where Eclipse compiles your Java source, is preselected,
as well as all libraries in the build path (eg. Java runtime jars, ejb3-persistence.jar and
jboss-ejb3x.jar):
The next panel lets you configure custom generation templates.
Those templates are a mix of the JSP syntax and the Groovy language.
If you need specific code generation, you may write your own template, select one template in the displayed tree, and click on the Edit button:
In the above example, a class: protocol is used because all standard templates are available in the classpath.
Alternatively, you may use the file: protocol to load your template from the filesystem. These templates can be
specified either by using absolute paths (eg. file:/absolute/path/to/mytemplate.gsp) or paths relative to your
current Eclipse project root directory (eg. path/to/mytemplate.gsp).
Clicking the Use Tide button will configure Tide specific templates for entity beans. Use it if you are configuring the builder for a Tide project.
Clicking the Use LCDS button will configure LCDS specific templates for basic beans and entity beans, and
remove all templates for enum and remote destination services. It will also configure a LCDS specific AS3TypeFactory in the "Options"
panel (see picture below). The LCDS templates generate AS3 beans that can be either [Bindable] (default) or [Managed]. If you need [Managed]
beans, use the following parameter with your include pattern: [managed=true]. For example, your include patterns could be:
If you want to use a single file generation policy (ie. no "Base" and inherited files), you can reconfigure the templates by using
class:org/granite/generator/template/lcdsStandaloneBean.gsp as the only template, removing the base template.
WARNING: when switching from dual templates to a single one, be sure to remove and backup any previously generated files.
The last panel lets you configure various options:
Some explanations:
UID Property Name: If you want your AS3 to implement mx.core.IUID, you must tell the generator
the name of the Java field that contains this UID; use the same name in all your beans. Default is to search for field named uid.
AS3TypeFactory Class: You may use this option to configure a custom factory for special type support.
See Handling Custom Data Types for a detailed example. If you configure this,
you must add your class in the Classpath panel.
EntityFactory Class: You may use this option to configure a custom factory for special entity support.
Setting this field to org.granite.generator.as3.BVEntityFactory is useful if you want to use the GraniteDS validation framework.
See Bean Validation (JSR-303) for details.
RemoteDestinationFactory Class: You may use this option to configure a custom factory for special service support.
You could for example implement a specific factory to analyze services for a particular framework.
"Show debug informations in console": If enabled, Gas3 will display more information during the generation process.
"Generate a Flex Builder configuration file": If enabled, Gas3 will generate a granite-flex-config.xml
that may be used in your compiler options. Useful to make sure that all generated AS3 beans will be included in your SWF. Note that
all AS3 files present in a Gas3 output directory (even those which are not generated) will be added to the config file.
"Use org.granite.math.Long": If enabled, Gas3 will generate AS3 Long properties for
Java long or Long properties. See Big Number Implementations for details.
"Use org.granite.math.BigInteger": If enabled, Gas3 will generate AS3 BigInteger properties for
Java BigInteger properties. See Big Number Implementations for details.
"Use org.granite.math.BigDecimal": If enabled, Gas3 will generate AS3 BigDecimal properties for
Java BigDecimal properties. See Big Number Implementations for details.
When you have finished with the wizard, a first generation process will start and you should see something like this in the Eclipse console:
If you need to change your configuration later, you can right-click on your project, select the Properties item,
and you'll be able to modify all GraniteDS Eclipse Builder configuration options:
The panels are exactly the same as those of the wizard and the above documentation applies.
You can use the GraniteDS builder with Flex Builder provided that the three builders (Java, Granite, and Flex) are configured in the correct order.
It will work without modification if you first create a Java project, add the Flex project nature, and then the GraniteDS nature; the builder will
make sure that builders are setup in the correct order. Otherwise, you may have to change this order. Right-click on your project, select the
Properties item, and select the Builders entry:
The order (Java / Granite / Flex) in the above picture is the correct one.
When you have configured your project to use the GraniteDS Eclipse Builder, you may cancel any further generation processes by removing the nature:
Note that the hidden configuration file .granite in your project is not removed by this action and you must delete it manually.
Otherwise, it will be reused whenever you add the nature again.
The main purpose of the builder is to generate AS3 files based on Java sources which are added or modified. When a Java source file is deleted or renamed, the builder will append to the name of all potentially AS3 generated files a suffix composed of a dot, the current system millisecond since epoch (1/1/1970) and an additionnal extension ".hid". The idea behind these renaming operations is to make sure that the Flex compilation will detect errors if these classes are used in the project (easing refactoring) and to ensure that any manual editing you have made in these classes is recoverable.
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.
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 Gas3 ant task as follows:
<taskdef name="gas3" classname="org.granite.generator.ant.AntJavaAs3Task"/>
To launch a build process with Gas3 targets, you should go to your Java source root directory and type something like:
$ ant -lib /gas3libs -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.
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.as3">
<gas3 outputdir="as3">
<classpath>
<pathelement location="classes"/>
</classpath>
<fileset dir="classes">
<include name="com/myapp/entity/**/*.class"/>
</fileset>
</gas3>
</target>
As you can notice, Gas3 generates AS3 beans from Java compiled classes.
You may use multiple Ant filesets in order to specify for which JPA classes you want to generate AS3 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 Gas3 in which directory AS3 beans will be generated (e.g., ./as3).
This path is relative to your current project directory and Gas3 will create subdirectories for packages.
AS3 beans will by default have the same package hierarchy as Java classes, with the same subdirectories as well.
For each JPA entity (say com.myapp.entity.MyEntity), Gas3 will generate two AS3 beans:
org.entity.MyEntityBase.as: This bean mainly contains fields, getters, setters, and IExternalizable methods
(readExternal/writeExternal). This file is generated if it does not exist or if it is outdated.
org.entity.MyEntity.as: 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 AS3 classes from an existing jar. Note that
the jar must be in the classpath:
<target name="generate.as3">
<gas3 outputdir="as3">
<classpath>
<pathelement location="lib/myclasses.jar"/>
</classpath>
<zipfileset src="lib/myclasses.jar">
<include name="com/myapp/entity/**/*.class"/>
</zipfileset>
</gas3>
</target>
You may tell Gas3 to generate AS3 classes with a different package and directory structure than the corresponding Java classes ones.
<gas3 ...>
<classpath .../>
<fileset .../>
<translator
java="path.to.my.java.class"
as3="path.to.my.as3.class" />
<translator
java="path.to.my.java.class.special"
as3="otherpath.to.my.as3.class.special" />
...
</gas3>
Gas3 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.as3.class, while path.to.my.java.class.special
will use a specific translation (otherpath.to.my.as3.class.special).
If you use a special Java2As3 converter, you must take care of packages translations.
See org.granite.generator.as3.DefaultJava2As3 implementation for details.
Gas3 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 gas3 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.
Here is the complete list of Gas3 node attributes:
outputdir and baseoutputdir: We have already seen the outputdir attribute
in basic usage. baseoutputdir lets you define a custom output directory for your "Base" generated files.
The default is to use the same directory as specified by the outputdir attribute.
uid: If you want your AS3 to implement mx.core.IUID, you must tell the generator
the name of the Java field that contains this UID. By default, Gas3 will search for a field named uid.
You may change this by adding a uid="myUid" attribute to the gas3 node.
If Gas3 does not find this uid, it will be silently ignored.
tide: Should we use a Tide specific template instead of the standard base template used for entity beans
(true or false, defaut is false). Setting this attribute has no effect
if you use a custom entity base template. See below.
entitytemplate and entitybasetemplate: Templates used for classes annotated with
@Entity or @MappedSuperclass.
interfacetemplate: Template used for Java interfaces.
beantemplate and beanbasetemplate: Templates used for other Java classes including @Embeddable.
enumtemplate: Template used for java.lang.Enum types.
remotetemplate and remotebasetemplate: Templates used for server services (EJB3, Spring or Seam services).
as3typefactory: You can plug your own org.granite.generator.as3.As3TypeFactory implementation
in order to add support for custom types. For example, if you have configured a custom Joda time converter,
you may extend Gas3 accordingly for this custom type. Just extend the org.granite.generator.as3.DefaultAs3TypeFactory class
and return org.granite.generator.as3.As3Type.DATE when you encounter a Joda DateTime instance.
See Handling Custom Data Types for a detailed example.
entityfactory: Class used to introspect specific entity properties or metadata (default is
org.granite.generator.as3.DefaultEntityFactory).
You may also use the built-in org.granite.generator.as3.BVEntityFactory in order to replicate bean validation annotations
into your AS3 model Bean Validation (JSR-303).
remotedestinationfactory: Class used to introspect specific service properties or metadata (default
is org.granite.generator.as3.DefaultRemoteDestinationFactory).
transformer: Class used to control the generation process (very advanced use).
externalizelong: should we write AS3 Long variables
(see Big Number Implementations). Default is false.
externalizebiginteger: should we write AS3 BigInteger variables
(see Big Number Implementations). Default is false.
externalizebigdecimal: should we write AS3 BigDecimal variables
(see Big Number Implementations). Default is false.
For example:
<target name="generate.as3">
<gas3
outputdir="as3"
baseoutputdir="base_as3"
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"
as3typefactory="path.to.MyAs3TypeFactory"
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 as3typefactory, entityfactory, remotedestinationfactory
or transformer attribute, you must configure the classpath in order to make your custom classes available to the Gas3 engine;
either use the classpath attribute in the taskdef declaration or in the gas3 call.
The Gas3 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>graniteds21</generatorToUse>
<baseOutputDirectory>${project.build.directory}/generated-sources</baseOutputDirectory>
<outputDirectory>${project.build.directory}/../src/main/flex</outputDirectory>
<extraOptions>
<tide>true</tide>
<uid>uid</uid>
<entityFactory>org.granite.generator.as3.BVEntityFactory</entityFactory>
<outputEnumToBaseOutputDirectory>false</outputEnumToBaseOutputDirectory>
</extraOptions>
<includeJavaClasses>
<include>${package}.entities.**</include>
<include>${package}.services.*Service</include>
</includeJavaClasses>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</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.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>
Gas3 templates are based on a specific implementation of Groovy Templates. As such, all Groovy template documentation should apply.
The reason why the standard Groovy implementation was not used was the lack of comments support (<%-- ... --%>)
and some formatting issues, especially with platform specific carriage returns. This may now be fixed but it was not at that time.
While the language itself is already documented on Groovy site, there are two specific bindings (ie. global variables used in Gas3 templates) that should be referenced.
Template execution is a two-phase process. First, the template is transformed to a standard Groovy script (mainly with expressions
like print(...)); second, the Groovy script is compiled and executed. Of course, the result of the first transformation
and the compiled script is cached, so further executions with the same template are much faster.
There are two bindings available in Gas3 templates:
| Name | Type | Description |
|---|---|---|
gVersion | String | Version number of the generator, e.g., "2.0.0" |
jClass | Implementation of the JavaType interface | An object describing the Java class for which the generator is writting an ActionScript 3 class |
fAttributes | Map<String, String> | A map which contains key-value pairs, as specified in include patterns (Eclipse builder only) |
JavaType implementations (passed as the jClass parameter) can be of the following types:
| Type | Description |
|---|---|
JavaEntityBean |
A class defining a JPA entity bean (ie. a class annotated with a @Entity or with a
@MappedSuperclass persistence annotation)
|
JavaEnum |
A class defining a Java enum class
|
JavaInterface | A class defining a Java interface |
JavaRemoteDestination |
A class defining a remote service annotated with @RemoteDestination
|
JavaBean | A class defining all other Java classes |
The two bindings gVersion and jClass can be used in your templates as any
other Groovy script variables. For example:
// Generated by Gas3 v.${gVersion}.
package ${jClass.as3Type.packageName} {
public class ${jClass.as3Type.name} {
...
}
}
If you execute this template with Gas3 version 2.3.2 and a Java class named com.myapp.MyClass, the output will be:
// Generated by Gas3 v.2.3.2.
package com.myapp {
public class MyClass {
...
}
}
If you plan to write custom templates, you should have a look at the standard GraniteDS templates and API documentation of the JavaType
implementations listed above.
Let's have a look to the standard GraniteDS template for Java interfaces. You may also see all templates here:
<%--
GRANITE DATA SERVICES
Copyright (C) 2007-2008 ADEQUATE SYSTEMS SARL
This file is part of Granite Data Services.
Granite Data Services is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 3 of the License, or (at your
option) any later version.
Granite Data Services is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, see <http://www.gnu.org/licenses/>.
--%><%
Set as3Imports = new TreeSet();
for (jImport in jClass.imports) {
if (jImport.as3Type.hasPackage() &&
jImport.as3Type.packageName != jClass.as3Type.packageName)
as3Imports.add(jImport.as3Type.qualifiedName);
}
%>/**
* Generated by Gas3 v${gVersion} (Granite Data Services).
*
* WARNING: DO NOT CHANGE THIS FILE. IT MAY BE OVERWRITTEN EACH TIME YOU USE
* THE GENERATOR. INSTEAD, EDIT THE INHERITED INTERFACE (${jClass.as3Type.name}.as).
*/
package ${jClass.as3Type.packageName} {<%
///////////////////////////////////////////////////////////////////////////
// Write Import Statements.
if (as3Imports.size() > 0) {%>
<%
}
for (as3Import in as3Imports) {%>
import ${as3Import};<%
}
///////////////////////////////////////////////////////////////////////////
// Write Interface Declaration.%>
public interface ${jClass.as3Type.name}Base<%
if (jClass.hasSuperInterfaces()) {
%> extends <%
boolean first = true;
for (jInterface in jClass.superInterfaces) {
if (first) {
first = false;
} else {
%>, <%
}
%>${jInterface.as3Type.name}<%
}
}
%> {<%
///////////////////////////////////////////////////////////////////////////
// Write Public Getter/Setter.
for (jProperty in jClass.properties) {
if (jProperty.readable || jProperty.writable) {%>
<%
if (jProperty.writable) {%>
function set ${jProperty.name}(value:${jProperty.as3Type.name}):void;<%
}
if (jProperty.readable) {%>
function get ${jProperty.name}():${jProperty.as3Type.name};<%
}
}
}%>
}
}
The code for this template is rather simple, but it can be very tricky to distinguish between JSP-like expressions, Groovy code, and outputted ActionScript 3 code.
The first block, enclosed with <%-- --%>, is a template comment: it will be completely ignored at transformation
(Groovy template to Groovy script) time.
The second block, enclosed with <% %>, is plain Groovy code and will be outputed as is at transformation time.
Its purpose is to collect and sort all references to other classes so we can later write ActionScript 3 import statements.
Then, the ActionsScript 3 code template really begins with a comment (Gas3 version and warning) and is followed by a package and interface definition with superinterfaces, if any, and finally, by a loop over interface getters/setters. Note that comments like:
///////////////////////////////////////////////////////////////////////////
// Write Import Statements.
... are Groovy script comments. They will be in the Groovy script but you will not find them in the outputted ActionScript3 file.
After the first transformation (Groovy template to Groovy script), the rendered code will be as follows:
Set as3Imports = new TreeSet();
for (jImport in jClass.imports) {
if (jImport.as3Type.hasPackage() &&
jImport.as3Type.packageName != jClass.as3Type.packageName)
as3Imports.add(jImport.as3Type.qualifiedName);
}
print("/**\n");
print(" * Generated by Gas3 v${gVersion} (Granite Data Services).\n");
print(" *\n");
print(" * WARNING: DO NOT CHANGE THIS FILE. IT MAY BE OVERWRITTEN EACH TIME YOU USE\n");
print(" * THE GENERATOR. INSTEAD, EDIT THE INHERITED INTERFACE
(${jClass.as3Type.name}.as).\n");
print(" */\n");
print("\n");
print("package ${jClass.as3Type.packageName} {");
///////////////////////////////////////////////////////////////////////////
// Write Import Statements.
if (as3Imports.size() > 0) {
print("\n");
}
for (as3Import in as3Imports) {
print("\n");
print(" import ${as3Import};");
}
///////////////////////////////////////////////////////////////////////////
// Write Interface Declaration.
print("\n");
print("\n");
print(" public interface ${jClass.as3Type.name}Base");
if (jClass.hasSuperInterfaces()) {
print(" extends ");
boolean first = true;
for (jInterface in jClass.superInterfaces) {
if (first) {
first = false;
} else {
print(", ");
}
print("${jInterface.as3Type.name}");
}
}
print(" {");
///////////////////////////////////////////////////////////////////////////
// Write Public Getter/Setter.
for (jProperty in jClass.properties) {
if (jProperty.readable || jProperty.writable) {
print("\n");
if (jProperty.writable) {
print("\n");
print(" function set ${jProperty.name}(value:${jProperty.as3Type.name}):void;");
}
if (jProperty.readable) {
print("\n");
print(" function get ${jProperty.name}():${jProperty.as3Type.name};");
}
}
}
print("\n");
print(" }\n");
print("}");
</programlisting>
<para>
As you can notice, <literal>${...}</literal> expressions are resolved by the Groovy engine rather than the JSP-like engine.
It would have been possible to use expressions like <literal><%= ... %></literal>, that will result in a script where:
</para>
<programlisting role="JAVA">
print("package ${jClass.as3Type.packageName} {");
</programlisting>
<para>
.. would have been split into three lines:
</para>
<programlisting role="JAVA">
print("package ");
print(jClass.as3Type.packageName);
print(" {");
</programlisting>
<para>
This is just informative, as it does not change anything in the final result.
</para>
<para>
Then, for this Java source code:
</para>
<programlisting role="JAVA">
package com.myapp.entity.types;
public interface NamedEntity {
public String getFirstName();
public void setFirstName(String firstName);
public String getLastName();
public void setLastName(String lastName);
public String getFullName();
}
... you will get this output:
/**
* Generated by Gas3 v2.2.0 (Granite Data Services).
*
* WARNING: DO NOT CHANGE THIS FILE. IT MAY BE OVERWRITTEN EACH TIME YOU USE
* THE GENERATOR. INSTEAD, EDIT THE INHERITED INTERFACE (NamedEntity.as).
*/
package com.myapp.entity.types {
public interface NamedEntityBase {
function set firstName(value:String):void;
function get firstName():String;
function get fullName():String;
function set lastName(value:String):void;
function get lastName():String;
}
}
Because of the two transformation steps of the template (Groovy template to Groovy script source, then Groovy script source to pre-compiled Groovy script), there are two possible sources of error:
<% expression.
now TreeSet(); instead of new TreeSet();However, since Groovy is an interpreted language, you may get some other errors at execution time:
jClass.neme instead of jClass.name.
Whenever these kinds of errors occur, you'll find comprehensive error log in your Shell or Eclipse console. Note that when the error occurs after the first transformation, the Groovy script is printed with line numbers, as well as the Groovy compiler message. It is easy to find the erroneous line in the printed Groovy script, but you have to figure out the corresponding line in the original template:
[gas3] Generating: /dev/workspace/graniteds_ejb3/as3/com/myapp/entity/types/NamedEntityBase.as (output file is outdated)
[gas3] org.granite.generator.exception.TemplateCompilationException:Could not compile template: /interfaceBase.gsp
[gas3] 1 |
[gas3] 2 | Set as3Imports = now TreeSet();
[gas3] 3 |
[gas3] 4 | for (jImport in jClass.imports) {
[gas3] 5 | if (jImport.as3Type.hasPackage() && jImport.as3Type.packageName != jClass.as3Type.packageName)
[gas3] 6 | as3Imports.add(jImport.as3Type.qualifiedName);
[gas3] 7 | }
[gas3] 8 |
[gas3] 9 |
[gas3] 10 | print("/**\n");
[gas3] 11 | print(" * Generated by Gas3 v${gVersion} (Granite Data Services).\n");
[gas3] 12 | print(" *\n");
[gas3] 13 | print(" * WARNING: DO NOT CHANGE THIS FILE. IT MAY BE OVERWRITTEN EACH TIME YOU USE\n");
[gas3] 14 | print(" * THE GENERATOR. INSTEAD, EDIT THE INHERITED INTERFACE (${jClass.as3Type.name}.as).\n");
[gas3] 15 | print(" */\n");
[gas3] 16 | print("\n");
[gas3] 17 | print("package ${jClass.as3Type.packageName} {");
[gas3] 18 |
[gas3] 19 |
[gas3] 20 | ///////////////////////////////////////////////////////////////////////////
[gas3] 21 | // Write Import Statements.
[gas3] 22 |
[gas3] 23 | if (as3Imports.size() > 0) {
[gas3] 24 | print("\n");
[gas3] 25 |
[gas3] 26 | }
[gas3] 27 | for (as3Import in as3Imports) {
[gas3] 28 | print("\n");
[gas3] 29 | print(" import ${as3Import};");
[gas3] 30 |
[gas3] 31 | }
[gas3] 32 |
[gas3] 33 | ///////////////////////////////////////////////////////////////////////////
[gas3] 34 | // Write Interface Declaration.
[gas3] 35 | print("\n");
[gas3] 36 | print("\n");
[gas3] 37 | print(" public interface ${jClass.as3Type.name}Base");
[gas3] 38 |
[gas3] 39 |
[gas3] 40 | if (jClass.hasSuperInterfaces()) {
[gas3] 41 |
[gas3] 42 | print(" extends ");
[gas3] 43 |
[gas3] 44 | boolean first = true;
[gas3] 45 | for (jInterface in jClass.superInterfaces) {
[gas3] 46 | if (first) {
[gas3] 47 | first = false;
[gas3] 48 | } else {
[gas3] 49 |
[gas3] 50 | print(", ");
[gas3] 51 |
[gas3] 52 | }
[gas3] 53 |
[gas3] 54 | print("${jInterface.as3Type.name}");
[gas3] 55 |
[gas3] 56 | }
[gas3] 57 | }
[gas3] 58 |
[gas3] 59 |
[gas3] 60 | print(" {");
[gas3] 61 |
[gas3] 62 |
[gas3] 63 | ///////////////////////////////////////////////////////////////////////////
[gas3] 64 | // Write Public Getter/Setter.
[gas3] 65 |
[gas3] 66 | for (jProperty in jClass.properties) {
[gas3] 67 |
[gas3] 68 | if (jProperty.readable || jProperty.writable) {
[gas3] 69 | print("\n");
[gas3] 70 |
[gas3] 71 | if (jProperty.writable) {
[gas3] 72 | print("\n");
[gas3] 73 | print(" function set ${jProperty.name}(value:${jProperty.as3Type.name}):void;");
[gas3] 74 |
[gas3] 75 | }
[gas3] 76 | if (jProperty.readable) {
[gas3] 77 | print("\n");
[gas3] 78 | print(" function get ${jProperty.name}():${jProperty.as3Type.name};");
[gas3] 79 |
[gas3] 80 | }
[gas3] 81 | }
[gas3] 82 | }
[gas3] 83 | print("\n");
[gas3] 84 | print(" }\n");
[gas3] 85 | print("}");
[gas3]
[gas3] at org.granite.generator.gsp.GroovyTemplate.compile(GroovyTemplate.java:143)
[gas3] at org.granite.generator.gsp.GroovyTemplate.execute(GroovyTemplate.java:157)
[gas3] at org.granite.generator.as3.JavaAs3GroovyTransformer.generate(JavaAs3GroovyTransformer.java:119)
[gas3] at org.granite.generator.as3.JavaAs3GroovyTransformer.generate(JavaAs3GroovyTransformer.java:1)
[gas3] at org.granite.generator.Transformer.generate(Transformer.java:71)
[gas3] at org.granite.generator.Generator.generate(Generator.java:83)
[gas3] at org.granite.generator.ant.AntJavaAs3Task.execute(AntJavaAs3Task.java:327)
[gas3] at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:288)
[gas3] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[gas3] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
[gas3] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
[gas3] at java.lang.reflect.Method.invoke(Method.java:597)
[gas3] at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:105)
[gas3] at org.apache.tools.ant.Task.perform(Task.java:348)
[gas3] at org.apache.tools.ant.Target.execute(Target.java:357)
[gas3] at org.apache.tools.ant.Target.performTasks(Target.java:385)
[gas3] at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1329)
[gas3] at org.apache.tools.ant.Project.executeTarget(Project.java:1298)
[gas3] at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
[gas3] at org.eclipse.ant.internal.ui.antsupport.EclipseDefaultExecutor.executeTargets(EclipseDefaultExecutor.java:32)
[gas3] at org.apache.tools.ant.Project.executeTargets(Project.java:1181)
[gas3] at org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.run(InternalAntRunner.java:423)
[gas3] at org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.main(InternalAntRunner.java:137)
[gas3] Caused by: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed,Script1.groovy: 2: expecting EOF, found 'TreeSet' @ line 2, column 26.
[gas3] 1 error
The error at line 2, column 26 is:
[gas3] 2 | Set as3Imports = now TreeSet();
Finding the corresponding line in the original template should be straightforward.