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


[Previous page] [Next page]

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 to Diff. In that case, the $refine method in the javassist.gluonj.GluonJ class can be used:

((Diff)GluonJ.$refine(p)).sayHi("Paul");

The $refine method just performs type conversion to java.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 either test.Person or test.People.

  • @Refine("test.P*|test.Q*")
    matches class/interface names starting with either test.P or test.Q.

  • @Refine("test.Human?")
    matches the name of the classes that directly implement test.Human. Suppose that the test.Person class implements the test.Human interface (the implements clause of test.Person explicitly lists test.Human). Then this pattern matches test.Person but not its subclasses. If the test.Human interface has a sub-interface (i.e. an interface extending test.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 the java.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.

[Previous page] [Next page]