Application-specific pointcut designators
It is well known that pointcuts are often fragile. If the definitions
of classes woven with aspects are changed, such pointcuts are broken.
For example, a pointcut call("Rectangle#setX(..)") selects
method calls to the setX method in the Rectangle
class. This pointcut will not work if the name of the setX
method is changed after refactoring.
Some researchers suggest selecting a join point in a more algorithmic way. We have also suggested the same idea in our previous AspectJ-like language named Josh. In Josh, programmers can implement a new pointcut designator as a plug-in written in Java. Since Josh was implemented on top of our Java-bytecode transformation toolkit Javassist (www.javassist.org), programmers can exploit all the functions of Javassist for implementing a new pointcut designator that selects join points according to a complex algorithm. This is also true for GluonJ, which is also using Javassist for bytecode transformation. If programmers are familiar with programming with Javassist, they can easily implement a pointcut designator based on a complex algorithm.
updater pointcut designator
We below show how an updater pointcut designator is
implemented in GluonJ. The updater pointcut designator is
an example we showed in Section 4 of
our
paper about Josh published at AOSD 2004.
The updater pointcut designator takes two parameters:
a class name and a method name. Let us suppose the class name is
Shape and the method name is redraw.
Since the role of the redraw method
in Shape or its subclass
is to draw the figure of the shape object on a screen,
the redraw method
will read the values of some fields declared in the same class.
For example, the redraw method in a Line subclass
of Shape would read the values of fields
x0, y0,
x1, and y1, which determine the position of a
Line object.
The updater pointcut designator
selects a call to the methods that update the values of those fields.
Changes of such values would result in changes of the figure of the
shape object.
Therefore, the join points selected by updater are
calls to the methods that will change the figure of the called object.
Implementation
We below show the implementation of the updater pointcut
designator by using a utility class
javassist.gluonj.util.SimplePcNode.
The implementation is simple (the complete list is
javassist/gluonj/plugin/Updater.java):
package javassist.gluonj.plugin;
import javassist.*; // and more...
@Glue public class Updater {
@Refine static class Pcd2 extends Pcd {
public static Pointcut updater(String className, String methodName) {
Pointcut pc = make();
return ((Pcut2)pc).updater(className, methodName);
}
}
@Refine static class Pcut2 extends Pointcut {
public Pcut2() { super(null); }
public Pointcut updater(String className, String methodName) {
setTree(new UpdaterPc(className, methodName));
return this;
}
}
public static class UpdaterPc extends SimplePcNode {
// below show details..
}
}
The basic structure of the @Glue class named
Updater is the same as that of MetaTag.
The definition of the nested class
UpdaterPc, which is a tree-node class, is the following:
public static class UpdaterPc extends SimplePcNode {
private ClassPool cpool;
private String rootClassName;
private CtClass rootClass;
private String methodName;
private HashMap updatersMap;
public UpdaterPc(String cName, String mName) {
rootClassName = cName;
methodName = mName;
updatersMap = new HashMap();
}
/**
* Is invoked before GluonJ starts advice weaving.
*/
public void prepare(PatternParser p) throws WeaveException {
cpool = p.getClassPool();
try {
rootClass = cpool.get(rootClassName);
}
catch (NotFoundException e) {
throw new WeaveException(e);
}
}
/**
* Collects updater methods declared in the given class.
*/
public void inspect(CtClass clazz) throws WeaveException {
if (!clazz.subclassOf(rootClass))
return;
try {
String className = clazz.getName();
CtMethod m = clazz.getDeclaredMethod(methodName);
HashMap fields = accessedFields(className, m);
HashMap updaters = updateMethods(clazz, fields);
updatersMap.put(className, updaters);
}
catch (CannotCompileException e) {
throw new WeaveException(e);
}
catch (NotFoundException e) {
return;
}
}
/**
* Returns true if the given join point is a call to an updater method.
*/
public boolean match(MethodCall joinPoint, Residue[] residue)
throws WeaveException
{
String className = joinPoint.getClassName();
Object updaters = updatersMap.get(className);
if (updaters == null)
return false;
try {
return ((HashMap)updaters).get(joinPoint.getMethod()) != null;
}
catch (NotFoundException e) {
throw new WeaveException(e);
}
}
// and more methods...
}
}
The important methods in this class are three: prepare,
inspect, and match. The prepare
method is called just before GluonJ starts advice weaving. It performs
preparation tasks if they are needed. A PatternParser object
is used for parsing a class pattern or a method pattern to be a precompiled
form.
The inspect method is called whenever GluonJ starts
advice weaving on each class. It examines the definition of the given
class and collects the updater methods in that class. The list of the
collected methods is recorded in the updatersMap field.
The match method is called whenever a join point is found.
It must return whether or not the given method call is a call to one of
the updater methods collected by the inspect method above.
If the return value is true and (the value of the whole pointcut
expression is true), GluonJ inlines an advice body at that
method-call expression. Otherwise, GluonJ ignores that join point.
The match method (and the other two methods) is inherited
from the super class SimplePcNode. If this updater
pointcut designator
selects a different type of join points, another match method
must be overridden.
Usage
Now, let us use the updater pointcut designator.
Of course, the @Glue code shown above must be installed
in advance (see "Install everything").
The following is a sample program that will be woven with
a @Glue class:
package test;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class ShapeEditor {
private List shapes;
public ShapeEditor() {
shapes = new LinkedList();
}
public void addShape(Shape shape) {
shapes.add(shape);
}
public void paint() {
Iterator iter = shapes.iterator();
while (iter.hasNext())
((Shape)iter.next()).redraw();
}
public static void main(final String[] args) {
new ShapeEditor().run();
}
public void run() {
Point p = new Point(this, 11, 17);
p.move(5, 4);
p.setName("p1");
}
}
abstract class Shape {
protected ShapeEditor editor;
public Shape(ShapeEditor e) {
editor = e;
e.addShape(this);
}
abstract void redraw();
}
class Point extends Shape {
private int x;
private int y;
private String name; // not accessed by redraw()
public Point(ShapeEditor e, int x, int y) {
super(e);
this.x = x;
this.y = y;
}
public int getX() { return x; }
public void setX(int newX) { x = newX; }
public int getY() { return y; }
public void setY(int newY) { y = newY; }
public String getName() { return name; }
public void setName(String s) {
System.out.println("setName()");
name = s;
}
public void move(int dx, int dy) {
System.out.println("move()");
x += dx;
y += dy;
}
public void redraw() {
System.out.println("(" + x + ", " + y + ")");
}
}
The redraw method in the Point class
reads the values of the fields x and y
but it does not read the value of name.
Thus, if the following @Glue class is given,
calls to the methods that updates the values of
x or y are selected as join points.
package test;
import javassist.gluonj.*;
@Glue public class UpdateScreen {
@After("{ paint(); }")
Pointcut pc = Pcd.updater("test.Shape", "redraw");
}
The pointcut pc selects calls to method
setX, setY, or move.
Calls to the setName method are not selected.
When the main method in the ShapeEditor
is executed, only the call to move causes the execution
of the @After advice. Note that the @After
advice is executed at a caller site. In the example above, it is
executed within the main method in the ShapeEditor
class. Thus, the paint method is called on
the ShapeEditor object, which is
the this object in that scope.
Copyright (C) 2006 by Shigeru Chiba. All rights reserved.