Previous page

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.

Previous page


Copyright (C) 2006 by Shigeru Chiba. All rights reserved.