Chapter 5. AS3 Code Generator

Chapter 5. AS3 Code Generator

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

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 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.

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 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 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.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.

Installation

Download org.granite.builder_***.jar and drop it in your Eclipse plugins directory, making sure to remove any older versions. Then, restart Eclipse.

Adding the GraniteDS Nature and Configuration Wizard

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:

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 . 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:

  • path/to/managed/beans/*.java[managed=true]
  • path/to/bindable/beans/*.java

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:

The GraniteDS Project Properties Panel

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.

Using the GraniteDS Builder Together with Flex/Flash Builder

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.

Removing the GraniteDS Nature

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.

Java file deletion / renaming

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.

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 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.

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.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:

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>
        
Packages Translations

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).

Groovy Templates

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.

Advanced Options (Gas3 XML Attributes)

Here is the complete list of Gas3 node attributes:

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 . 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.

Template Bindings

There are two bindings available in Gas3 templates:

NameTypeDescription
gVersionStringVersion number of the generator, e.g., "2.0.0"
jClassImplementation of the JavaType interfaceAn object describing the Java class for which the generator is writting an ActionScript 3 class
fAttributesMap<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:

TypeDescription
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.

Sample Template

Let's have a look to the standard GraniteDS template for Java interfaces. You may also see all templates :


<%--
  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>&lt;%= ... %&gt;</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;
    }
}
        
Template Compilation and Execution Errors

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:

  • JSP-like syntax errors (first transformation): e.g., unclosed <% expression.
  • Groovy syntax errors (second transformation): e.g., now TreeSet(); instead of new TreeSet();

However, since Groovy is an interpreted language, you may get some other errors at execution time:

  • Mispelled expressions: e.g., jClass.neme instead of jClass.name.
  • Runtime exceptions: e.g., 0 / 0.

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.