...
| Code Block |
|---|
class A {
// Code common to all properties
final static Set PROPERTIES = new LinkedHashSet ()
void invokeListener (String property, event) {
...
}
// Code generated per property
final static String PROPERTY_X = 'x'
static {
PROPERTIES.add (PROPERTY_X)
}
List listeners
int x
void addXListener (listener) {
...
}
boolean removeXListener (listener) {
...
}
void setX (int x) {
if (x != this.x) {
int oldValue = this.x
this.x = x
invokeListener (PROPERTY_X, new PropertyChangeEvent (oldValue, x))
}
}
}//
|
| Note |
|---|
This example needs to be re-worked to conform to the JavaBeans specification regarding BoundPropertiesÂBoundProperties |
specifically:
- When using code completion, the additional fields and methods should be visible.
- The added code should be lean and fast
- It should not get in the way of existing user code, for example, it should be possible to define a custom setter which gets wrapped by the code above
- If a custom setter exists, it should be possible to invoke it before the comparison (so you can define setters which convert the value before assigning it)
...
SQL enhanced code is pretty similar to bound properties but more code is generated. The first step is to define the class which maps a database table to a Java object:
| Code Block |
|---|
@Entity
class Foo
{
@Id
@Column (type:java.sql.Types.INTEGER)
int id
@Column (type:java.sql.Types.CHAR, size:20)
String name
}
|
After this is compiled, I want to see a special field "SQL" which I can use to build database queries like so:
| Code Block |
|---|
def columns = [Foo.SQL.value, Foo.SQL.name]
def cond = Sql.WHERE () { Foo.SQL.id >= 5 && Foo.SQL.name != null }
def list = Sql.SELECT (columns, table:Foo.SQL.TABLE, where:cond, class:Foo.class)
|
This gets converted by the compiler into:
| Code Block |
|---|
def list = []
def _sql = "SELECT id, name FROM foo WHERE id >= ? AND name IS NOT NULL"
_sql = Sql.eachRow (_sql, [5]) {
Foo o = new Foo ()
o.id = it[0]
o.name = it[1]
list << o
}
|
The SQL object in Foo also gives access to the standard DAO methods like loading an object by its primary key:
| Code Block |
|---|
def foo = Foo.SQL.load (5) |
In addition to the simple bound property example, the AMP must also be able to note the usage of an annotated object, so it can convert the Groovy code into SQL at compile time (and possibly check it for mistakes).
Open Issues
Java 1.4/5
Groovy 1.x must run on Java 1.4. We must decide what to do with non-macro annotations, whether we want to support a switch to generate Java 5 classfiles (so Groovy can generate code for third party APTs like Hibernate)
It seems that it is possible to write annotations into Java 1.4 classfiles (see Commons Attributes). But the questions is: Is this futile? There are only a few tools which support annotations and Java 1.4.
In this light, it makes more sense to add a switch to allow Groovy to write Java 5 classfiles, so users stuck to 1.4 can still use it and Java 5 users can upgrade when they want to.
Expand or Pass On
The compiler needs a way to decide what to do with an official Java 5 annotation like javax.persistence.Entity which is defined in EJB3: Expand it as a macro or pass it on into the class file so a third party library/tool can process it later.
Here, the user might want to decide differently per class (i.e. handle most of these cases with Hibernate and some corner cases with her own AST macro).
For Groovy-specific macros, the solution is to add a marker interface to the macro annotation.
Options
- Add a config file to the compiler
- Users have to use a different annotation which implements both Groovy's marker interface and
javax.persistence.Entity