Introduction to GluonJ
Shigeru Chiba
Table of Contents
1. Why do you use GluonJ?
2. Writing a glue
3. Refinement (1)
4. Refinement (2)
5. Pointcut and Advice
6. Extending a glue
3. Refinement (1)
@Refine
specifies a refinement on an existing
class/interface.
In other words, it extends the definition of an existing class/interface.
Unlike the inheritance and mixin mechanisms, however, @Refine
directly modifies the existing class/interface definition.
@Refine
corresponds to the intertype declaration of AspectJ.
Suppose that the following class has been declared:
package test; public class Person { public String name; public void greet() { System.out.println("I'm " + name); } }
Let us extend this class definition for test.Person
:
@Glue class SayHello { @Refine static class Diff extends test.Person { public String message = "Hello"; public void greet() { System.out.println(message); } } }
This glue includes a static
nested class named
Diff
, which is annotated by @Refine
.
This static nested class is called a @Refine
class.
Only a static nested class can be a @Refine
class;
an inner class (i.e. non-static nested class) cannot be a
@Refine
class.
This nested @Refine
class named Diff
looks like a normal subclass of test.Person
. However, since
it is annotated by @Refine
, it directly modifies the
definition of Person
.
It appends a new field message
to the
Person
class. Also, it substitutes the
greet
method declared in Diff
for
the greet
method in the original test.Person
class.
The target class that a field and a method are appended to is
specified by the extends
keyword in the definition of
the @Refine
class.
The name of a @Refine
class has no special meaning. Other names
such as Person2
and Expand
are fine
as well as Diff
.
A subclass definition produces an extended class from its super class. The extended class and its original one coexist. On the other hand, a refinement produces an extended version of its super class (i.e. its target class) and furthermore substitutes it for the original super class. The extended class overrides the original one.
If the SayHello
glue shown above is woven,
the definition of Person
is changed by the @Refine
class to the following:
package test; public class Person { public String name; public String message = "Hello"; public void greet() { System.out.println(message); } }
Although the SayHello
glue shown above contains only one
@Refine
class, a @Glue
class can contain
any number of @Refine
classes as its member.
A @Refine
class can be declared as not a nested class
but a top-level class. The following program is equivalent to the
@Glue
class SayHello
shown above:
@Glue class SayHello { @Refine Diff refines; } public class Diff extends Person { public String message = "Hello"; public void greet() { System.out.println(message); } }
The field declaration annotated by @Refine
specifies
a refinement. The type of the declared field must be a refinement
class. This refinement class is used to extend the class
specified by extends
. In the case above,
the test.Person
class is modified according to
the Diff
class. The name of the field does not
have a special meaning. Any name can be given to it.
Add a new method
A @Refine
class can append a new method as well as
a field to its original class:
package test; public class Person { public String name; public void greet() { System.out.println("I'm " + name); } }
The following @Refine
class appends the sayHi
method to the test.Person
class.
package test; @Glue class Cheerful { @Refine static class Diff extends Person { public void sayHi(String toWhom) { System.out.println("Hi " + toWhom); } } public static class Test { public static void main(String[] args) { Person p = new Person(); ((Diff)p).sayHi("Paul"); } } }
To call the sayHi
method on a Person
object, the type of the object must be cast from Person
to Diff
. See the main
method in
the test.Cheerful.Test
class.
The sayHi
method will not be added
until the glue is woven after compilation.
At the source-code level, it is available only on the Diff
type.
After glues are woven,
the name of a @Refine
class is interpreted as being
equivalent to the name of its target class.
Thus, it is also possible to perform type conversion from a subclass
of Person
to Diff
.
Note: The name of a
@Refine
class is regarded as an alias of the target class only within the member classes of@Glue
classes.
Note: Some compilers do not allow the type cast from
Person
toDiff
. In that case, the$refine
method in thejavassist.gluonj.GluonJ
class can be used:
((Diff)GluonJ.$refine(p)).sayHi("Paul");
The
$refine
method just performs type conversion tojava.lang.Object
.
A method in a @Refine
class can call a method in its
target (i.e. its super class).
package test; @Glue class Cheerful2 { @Refine static class Diff extends Person { public void sayHi(String toWhom) { greet(); System.out.println("Hi " + toWhom); } public void greet() { super.greet(); System.out.println("How are you?"); } } }
super.greet()
invokes the method originally declared
in the Person
class. On the other hand, greet()
in the sayHi
method invokes the
greet
method declared in the Diff
class.
This semantics is the same as that of normal method overriding and
calls on super
.
If there are more than one @Refine
classes extending the
same class, each of them refines the target class in turn in the order
of the priority. A call on
super
reflects this semantics.
package test; @Glue class Greeting1 { @Refine static class Hi extends Person { public void greet() { System.out.println("Hi!"); super.greet(); } } } @Glue class Greeting2 { @Refine static class Wow extends Person { public void greet() { System.out.println("Wow!"); super.greet(); } } } @Glue class Greeting { @Include Greeting1 glue1; @Include Greeting2 glue2; }
If the Greeting
is woven, the two @Refine
classes Hi
and Wow
are applied in this order
to extend the Person
class. First, Hi
extends the greet
method in the Person
class.
Then, Wow
extends the greet
method that has
been modified by Hi
. Hence, super.greet()
in Wow
invokes the greet
method that
Hi
has extended.
The resulting behavior of
the greet
method is equivalent to the following one:
public void greet() { System.out.println("Wow!"); System.out.println("Hi!"); System.out.println("I'm " + name); }
Override a static
method
Unlike a regular class, a @Refine
class can
override a static
method in its super class.
For example,
package test; public class Recursive { public static int factorial(int n) { if (n == 1) return 1; else return n * factrial(n - 1); } }
The following @Refine
class overrides
the factorial
method originally declared in
the Recursive
class:
package test; @Glue class Iterative { @Refine static class Diff extends Recursive { public static int factorial(int n) { int f = 1; while (n > 1) f *= n--; return f; } } }
If this @Glue
class is woven, a call to
Recursive.factorial
computes the factorial of
a given number by using the implementation described in
the @Refine
class Diff
.
A static
method in a @Refine
class
can call the method overridden by that static
method.
package test; @Glue class Terminator { @Refine static class Diff2 extends Recursive { public static int factorial(int n) { if (n >= 1) return Recursive.factorial(n); // like a call on super. else return 0; } } }
A call to the Recursive.factorial
method invokes
the original implementation of factorial
. Recall
that, unless the caller site is within the @Refine
class
Diff2
,
a call to the Recursive.factorial
method invokes
the implementation declared in the @Refine
class
instead of the original one. This semantics has been borrowed
from method calls
through super
, which allows a method to call
an overridden method declared in its super class.
Interfaces and fields
A @Refine
class can append a field and an implemented
interface to its target class.
package test; public class Person { public String name; public void greet() { System.out.println("I'm " + name); } }
The following @Refine
class appends the three new
members to the test.Person
class.
They are the who
method,
the Nameable
interface, and
the counter
field.
package test; interface Nameable { void who(); } @Glue class WhoAreYou { @Refine static class Diff extends Person implements Nameable { public int counter = 0; @Super public String name = "Anonym"; public void who() { counter++; greet(); } } }
The test.Nameable
interface is appended to the
interfaces that the test.Person
class implements.
The who
method included in the Nameable
interface is also appended by this @Refine
class.
The counter
field is also appended
to the Person
class. The initial value of that field
is 0
. On the other hand, the name
field is not
appended because the Person
class already includes
that field. The @Refine
class overrides
the name
field declared in
the Person
class. @Super
represents that
the field overrides it.
Note:
@Super
is optional. However, if a field with@Super
does not override a field in the target class, then an error will be reported.
After the @Glue
class
above is woven, the initial value of the name
field
in Person
is set to "Anonym"
.
In the current implementation, the new initial value is assigned
to the field just after the execution of a constructor of
Person
finishes. During the execution of the constructor,
the field holds the original initial value. If the constructor calls
another constructor by this()
, the new initial value is
assigned just after the call to another constructor finishes.
A @Refine
class overriding a field in its super class
can change the initial value of the field and also append annotations to
the field. For example, the following @Refine
class
appends an annotation @Setter
to the name
field in the test.Person
class:
package test; public @interface Setter {} @Glue class Annotater { @Refine static class Diff extends Person { @Setter public String name; } }
Note that the name
field above does not
have the @Super
annotation. If it has @Super
,
@Super
is also appended to the name
field
in test.Person
.
GluonJ allows a @Refine
interface as well as
a @Refine
class.
A @Refine
interface can append a new method to its
super interface. It can also append a new implemented interface
to its super interface.
For example, the following @Refine
interface appends
a new interface:
package test; @Glue class Annotater { @Refine static interface Diff extends Nameable, Cloneable { } }
This modifies the Nameable
interface. The second
super interface Cloneable
is appended to the
Nameable
interface as its super interface.
Note that the target of a @Refine
interface
is the first interface among
the interfaces following extends
. Thus, the second, third, ...
interfaces are appended to the first one as its super interfaces.
Constructor of a @Refine
class
Since a @Refine
class is not a regular class, the
definition of @Refine
classes must follow the
following restrictions:
No subclass of a
@Refine
class can be defined.No instance of a
@Refine
class can be made.The constructor of a
@Refine
class must be only the default constructor that takes no parameter.
If a target class does not have the default constructor that
takes no parameter, the constructor of a @Refine
class
must call non-default constructor of the target class (i.e. its super class).
However, this call is ignored when the @Refine
class
is woven. For example,
package test; public class Counter { private int counter public Counter(int c) { counter = c; } public void decrement() { if (--counter <= 0) throw new RuntimeException("Bang!"); } } @Glue class Increment { @Refine static Diff extends Counter { private int delta; public Diff() { super(0); delta = 1; } public void increment() { counter += delta; } } }
when the @Refine
class Diff
is woven,
a copy of the contents of
the constructor of Diff
is appended to the constructor
of Counter
. However, the call to the constructor of the
super class is removed from the copy. For example, super(0)
in the constructor of Diff
is not copied. The extended
constructor of Counter
by the @Refien
class
is as the following:
public Counter(int c) { counter = c; delta = 1; // copied from the @Refine class }
Accessing private members
A @Refine
class can access private methods and
fields declared in its target class
only if it is annotated by @Privileged
. This feature
is provided mainly for logging and tracing;
you should avoid using it if possible because it
violates the encapsulation property.
If a @Refine
class declares a private field with the
same name as a private field in the target class, that field is
identical to the private field in the target class. Accesses to that
field are regarded as accesses to the field in the target class.
package test; public class Person { private String name; public void setName(String newName) { name = newName; } private String getName() { return name; } public String whoAreYou() { return "I'm " + getName(); } }
package test; @Glue class PersonExtender { @Privileged @Refine static class Diff extends Person { @Super private String name; public String whoAreYou() { return "My name is " + name; } } }
In the example above, the name
field in the Diff
class is identical to the name
field in the Person
class.
Hence, the access to name
in the whoAreYou
method
is regarded as an access to the private field name
in
the Person
.
For example, if the @Glue
class above is woven,
Person p = new Person(); p.setName("Bill"); String s = p.whoAreYou();
The value of s
is "My name is Bill"
.
It is not "I'm Bill"
or "My name is null"
.
A @Refine
class can also override the initial value
of a private field declared in its target class:
package test; @Glue class PersonExtender2 { @Privileged @Refine static class Diff2 extends Person { @Super private String name = "Unknown"; } }
This @Refine
class changes the initial value of
the name
field declared in the Person
class.
The new value is "Unknown"
.
Overriding a private method is also allowed if a @Refine
class is privileged:
package test; @Glue class PersonExtender3 { @Privileged @Refine static class Diff3 extends Person { private String name; private String getName() { return name.toUpperCase(); } } }
If the Person
class is woven with this @Glue
class, the whoAreYou
method on a Person
object
calls the getName
method implemented in the Diff3
class.
Finally, a method in a @Refine
class can call a
private method in its target class if the @Refine
is
privileged.
package test; @Glue class PersonExtender4 { @Privileged @Refine static abstract class Diff4 extends Person { @Super("getName") abstract String super_getName(); public String whoAreYou() { return "My name is " + super_getName(); } } }
To call a private method getName
in Person
,
an abstract method with @Super
must be declared
in the @Refine
class.
This abstract method named super_getName
must have
the same parameter types as
getName
in the target class.
The argument of @Super
must be the name of
the private method that we want to call.
The call to super_getName
is translated into a call
to the getName
in Person
.
A method in a @Refine
class can call a
private method overridden by that method:
package test; @Glue class PersonExtender5 { @Privileged @Refine static abstract class Diff5 extends Person { @Super("getName") abstract String super_getName(); private String getName() { return super_getName().toUpperCase(); } } }
Note that the getName
method in the @Refine
class overrides the private method getName
in the target
class Person
. This overriding method getName
calls the super_getName
method declared in the same class.
This call is translated into a call to the getName
implemented
in the target class Person
.
Override a final class
If the class you want to modify is a final
class,
you cannot define a subclass of that class.
You must give the name of that class directly to @Refine
as an argument. For example,
package test; public final class Person { public String name = "Ellen"; public void greet() { System.out.println(name + "!"); } }
The Person
class is final
.
Thus, a @Refine
class for Person
must be as following:
@Glue class Cheerful { @Privileged @Refine("test.Person") public static class Diff { @Super public String name; public void greet() { System.out.println("Hi " + name); } } }
The argument to @Refine
is a fully qualified class name.
The Diff
class above overrides the greet
method in test.Person
. It can override the greet
method even if it is a final
method.
To access name
in
test.Person
, a field with the same name is declared
with @Super
in the @Refine
class.
This is the same technique we used for accessing a private
member.
Summary
The semantics of the extension by a @Refine
class
is almost the same as the extension by a subclass. Exceptions are:
A static method can be overridden.
The initial value of a field can be overridden.
The annotations of a field can be appended.
The constructor of a
@Refine
class must be the default constructor.A
@Refine
class annotated by@Privileged
can access and override private members in its target class.
@Refine
can be used for extending an interface.
A @Refine
interface refines the definition of the
first interface that follows the extends
keyword.
4. Refinement (2)
A @Refine
class can be applied to multiple
target classes specified by using a wild card. It can also
conditionally override a method in the target class.
We below describe details of these features.
Multiple targets
We have already seen that @Refine
can take a parameter,
which specifies its target class.
If @Refine
takes a parameter, then the super class of
that @Refine
must be java.lang.Object
;
it is not regarded as the target class.
The parameter to @Refine
can be used not only to modify a final
class but also
to target multiple classes. Because the parameter to @Refine
can include a wild card, the @Refine
can be applied to
multiple classes matching the pattern given as the parameter to
@Refine
.
For example,
package test; @Glue class Cheerful { @Refine("test.P*") public static class Diff implements Human { public void greet() { System.out.println("Hello!"); } } }
package test; public interface Human { void greet(); }
The target class of the @Refine
class Diff
is the classes matching the given pattern "test.P*"
, such
as test.Person
and test.People
.
The greet
method is appended to all the target classes.
If a @Refine
class has more than one target classes,
then the name of the @Refine
class is not regarded
as an alias of the target class name. Thus, we cannot use a type cast
for calling the method added by the @Refine
class:
public void test() { Person p = new Person(); ((Diff)p).greet(); // error }
The type cast from Person
to Diff
is not valid
even within the same Glue
class. To call the greet
method, we have to convert the type of p
from Person
to Human
, which was added to the interfaces implemented by
Person
:
public void test() { Person p = new Person(); ((Human)p).greet(); // OK }
Note that the @Refine
class Diff
has added
the Human
interface to the list of the implemented interfaces.
If a @Refine
class has multiple target classes, it must
add an interface to the targets so that the methods added by the
@Refine
class can be accessible through that interface.
GluonJ currently provides *
, |
, and
?
as a wild card for @Refine
.
|
means disjunction
(i.e. OR). It is used to enumerate multiple class/interface
names. ?
can follow an interface name. If a pattern
includes such an interface name ending with ?
, then it matches
all the classes that directly implement that interface type.
For example,
@Refine("test.Person|test.People")
matches eithertest.Person
ortest.People
.@Refine("test.P*|test.Q*")
matches class/interface names starting with eithertest.P
ortest.Q
.@Refine("test.Human?")
matches the name of the classes that directly implementtest.Human
. Suppose that thetest.Person
class implements thetest.Human
interface (theimplements
clause oftest.Person
explicitly liststest.Human
). Then this pattern matchestest.Person
but not its subclasses. If thetest.Human
interface has a sub-interface (i.e. an interface extendingtest.Human
), then the pattern also matches a class directly implementing that sub-interface.
Overriding multiple methods
A method in a @Refine
class can modify multiple
methods declared in its target class.
package test; @Glue class Cheerful { @Refine public static class Diff extends Person { @Overwrite("say*") public void newSay() { System.out.println("Hello!"); } } }
This @Refine
class refines the target class
Person
. Because the newSay
method is annotated
by @Overwrite
, it overrides not the newSay
method in Person
but all the methods that have the name
matching the given pattern "say*"
and have the same signature
as newSay
.
Only the methods in Person
are overridden; the methods declared
in a super class of Person
are not (even if their names match
the pattern).
In Person
, there must not be a newSay
method
with the same signature; you must choose a new unique name for a
@Overwrite
method.
Note: The annotation is not
@Override
but@Overwrite
.@Override
is an annotation included in thejava.lang
package.
To call the original method that has been overridden
by a method with @Overwrite
,
we must declare a forwarder method by @SuperOf
.
Note that we cannot write super.newSay()
because the target class Person
does not declare
a newSay
method.
package test; @Glue class Cheerful { @Refine public static class Diff extends Person { @SuperOf("newSay") abstract void super_newSay(); @Overwrite("say*") public void newSay() { System.out.println("Hello!"); super_newSay(); } } }
The super_newSay
method is the forwarder method
of the newSay
method because super_newSay
is an abstract method annotated by @SuperOf
(note that it is not
@Super
). The argument to @SuperOf
is the
name of the corresponding @Overwrite
method.
The forwarder method must have the same signature that the
corresponding @Overwrite
method has.
There is no special naming rule for the forwarder method;
you can choose any name for it.
The call to the forwarder
method from newSay
invokes the original method.
If the Person
class has methods sayHello
and sayHi
, this @Overwrite
method modifies
them so that a call to sayHello
and sayHi
will first print "Hello!"
and then execute the original
body of each method.
Quantified overriding
A @Refine
class can
conditionally override a method in its target class.
We call this quantified overriding.
For example,
package test; public class Person { public String name = "Paul"; public void greet() { System.out.println("I'm " + name); } }
package test; @Glue class SayHiToMain { @Refine static class Diff extends test.Person { @Within("test.Main") public void greet() { System.out.println("Hi"); super.greet(); } } }
The Diff
class overrides the greet
method in the Person
class. However, this overriding
is effective only when the greet
method is called
from the test.Main
class. For example,
package test; class Main { public void test(Person p) { p.greet(); // "Hi" is printed } } class Test { public void start(Person p) { p.greet(); // "Hi" is not printed } }
The call from the test
method invokes
the greet
method declared in the @Refine
class Diff
.
On the other hand, the call from the start
method does
not invoke it because start
is not a method in test.Main
.
@Within
The argument to @Within
can be not only a fully-qualified class name
but also a method name.
If it is a method name, the fully-qualified class name and
method name must be separated by #
. For example,
@Within("test.Point#move(int, int)") @Within("test.Point#move(..)")
The arguments to @Within
above represent
the move
method in the test.Point
class.
The upper one matches only the move
method
taking two int
parameters while the lower one
matches a move
method taking any
types of parameters.
For more details,
see method pattern.
@If
Another annotation for quantified overriding is @If
.
The method with this annotation overrides the target's method
only while the Java expression
given to @If
is true
.
package test; @Glue class SayHiIf { @Refine static class Diff extends test.Person { @If("$0.age > 20") public void greet() { System.out.println("Hi"); } } }
The greet
method in the @Refine
class
is executed when the greet
method in Person
is called on an object p and the value of
the age
field of p is more than 20.
The special variable $0
refers to the target object
that the method is called on.
The expression given to @If
is evaluated in the
context of the client (or method caller) side.
Call pointcuts v.s. quantified overriding
The quantified overriding allows us to use a @Refine
class as an alternative to the call
pointcut of AspectJ.
For example, the following advice in Aspectj prints "Hi"
only when a method within the test.Main
class calls the
greet
method in the Person
class:
void around(): call(void Person.greet()) && within(test.Main) { System.out.println("Hi"); proceed(); }
This advice is mostly equivalent to the
following @Refine
class:
@Refine class Diff extends test.Person { @Within("test.Main") public void greet() { System.out.println("Hi"); super.greet(); } }
Although both changes the behavior of the greet
method, the behavior will differ between AspectJ and GluonJ
if the apparent type of the
target object is a super class of the Person
class.
Suppose that Person
is a subclass of Mammal
and Mammal
also declares the greet
method.
Then,
Person p = new Person(); Mammal m = p; m.greet(); // Will "Hi" be printed?
The call to the greet
method on m
will not invoke the advice in AspectJ shown above
because the apparent type of m
is not Person
.
On the other hand, the call to greet
on m
invokes the greet
method declared in the @Refine
class. "Hi"
will be printed.
@Client
In AspectJ, the combination of call
and this
pointcuts enables us to pass the client (or caller) object to the
advice:
void around(test.Main from): call(void Person.greet()) && within(test.Main) && this(from) { System.out.println("Hi " + from.toString()); proceed(); }
Within the body of the advice,
a reference to the test.Main
object that has called
the greet
method is available by from
.
In GluonJ, the above advice is described as the following:
package test; @Glue class SayHi { @Refine static class Diff extends test.Person { public void greet(@Client test.Main from) { System.out.println("Hi " + from.toString()); super.greet(); } } }
Note that the first parameter to greet
, which is
annotated by @Client
.
This parameter is a reference to the client object that calls
the greet
method on a Person
object.
It is excluded from the method signature used for
identifying the overridden method. In fact, the greet
method in the @Refine
class overrides the greet
method that is declared in the Person
class and takes
no parameters. It never overrides the greet
method
taking a test.Main
object as a parameter.
@Client
is another annotation for quantified overriding.
In the example above, overriding by the greet
method
in the @Refine
class is effective only when the
type of the client object is a test.Main
.
If the greet
method is called within the body of
a method not declared in test.Main
, then the overriding
by the @Refine
class above will not be effective.
@Get and @Set
A @Refine
class can change the behavior of
field accesses.
package test; public class Person { public String name = "Paul"; public void greet() { System.out.println("I'm " + name); } }
package test; @Glue class Accessor { @Refine static class Diff extends test.Person { @Get("name") public String get_name() { return "Mr. " + name; } @Set("name") public void set_name(String s) { System.out.println("name = " + s); name = s; } } }
This @Refine
class defines a getter method
and a setter method for the name
field of
the Person
class. The arguments to @Get
and @Set
must be the names of fields of the target
class. There is no naming rule for the name of the getter and setter
methods; any method name is valid.
The getter and setter methods are automatically substituted
for direct accesses to the field (unless the accesses are within
the @Glue
class). For example,
package test; public class PersonTest { public void test() { Person p = new Person(); p.name = "Black"; System.out.println(p.name); } }
Executing the test
method invokes set_name
and get_name
methods in the @Refine
class.
The assignment to p.name
does not result in a direct field
access. Instead, it invokes the set_name
method, which eventually sets the name
field to
"Mr. Black"
.
Reading p.name
also invokes the get_name
method.
The name
field can never be directly read; it is accessed
through its getter method.
Note that this substitution for field accesses is not applied to the
method bodies annotated by either @Get
or @Set
.
Thus, infinite regression never happens.
Copyright (C) 2006-2007 by Shigeru Chiba. All rights reserved.