3. Gas3 Code Generator
Overview
Externalizers usage in GraniteDS requires ActionScript3 beans that implement the flash.utils.IExternalizable interface for externalized JavaBeans. Writing and maintaining these ActionScript3 beans is tedious and a source of many errors. In order to solve this problem and accelerate Flex/J2EE application development, GraniteDS comes with an ActionScript3 code generator that writes AS3 beans for all externalized JavaBeans, with specific support for lazily loaded EJB 3 entities.
Additionally this ActionScript3 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 8. Bean Validation (JSR-303)) and may also be configured to generate Long, BigInteger and BigDecimal variable for their Java equivalents (see 2. Big Number Implementations).
This generator (named GAS3) is implemented as an Eclipse Builder 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.
The next sections introduce both Eclipse Builder 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.
"Base" & Inherited ActionScript3 Classes
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 lets 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:
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; } }
/** * Generated by Gas3 v2.0.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 { } }
/**
* Generated by Gas3 v2.0.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:
| 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 |
Note that for Java classes, relevant timestamp is the last modified time of the .class file, not the .java file.
Java Classes & Corresponding Templates
Here is the summary of templates used by the generator depending on the kind of Java class it encounters:
| Java Class | Template | Base Template |
|---|---|---|
| JPA entity beans: all classes annotated by @Entity and @MappedSuperclass | entity.gsp | entityBase.gsp, or tideEntityBase.gsp for Tide projects |
| Java enums | enum.gsp | (none) |
| Java interfaces | interface.gsp | (none) |
| Java services: all classes annotated by @RemoteDestination | remote.gsp | remoteBase.gsp, or tideRemoteBase.gsp for Tide projects |
| Java events: all classes annotated by @TideEvent | bean.gsp | beanBase.gsp |
| All other Java classes | 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.
Known Limitations
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 files, if you have removed or renamed a Java class, the old AS3 class versions must be manually removed.
