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
@Refineclass is regarded as an alias of the target class only within the member classes of@Glueclasses.
Note: Some compilers do not allow the type cast from
PersontoDiff. In that case, the$refinemethod in thejavassist.gluonj.GluonJclass can be used:
((Diff)GluonJ.$refine(p)).sayHi("Paul");
The
$refinemethod 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:
@Superis optional. However, if a field with@Superdoes 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
@Refineclass can be defined.No instance of a
@Refineclass can be made.The constructor of a
@Refineclass 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
@Refineclass must be the default constructor.A
@Refineclass annotated by@Privilegedcan 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.Personortest.People.@Refine("test.P*|test.Q*")
matches class/interface names starting with eithertest.Portest.Q.@Refine("test.Human?")
matches the name of the classes that directly implementtest.Human. Suppose that thetest.Personclass implements thetest.Humaninterface (theimplementsclause oftest.Personexplicitly liststest.Human). Then this pattern matchestest.Personbut not its subclasses. If thetest.Humaninterface 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
@Overridebut@Overwrite.@Overrideis an annotation included in thejava.langpackage.
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.