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.