Modular Programming with Join Point Types and Polymorphic Pointcuts

Overview

Table of contents

Aspect-oriented programming a la AspectJ is known to cause some modularity problems. In particular, aspects can change the behaviour of classes without the classes knowing, and the programmers of classes must observe interfaces defined by aspects unknown to them. The following figure shows the current dependencies and the single-sided interface between aspect X and classes A, B, and C. Note that the interface materializes in the pointcut of the aspect. Although the pointcut can be moved out of the aspect into another one or even to a class, the affected classes contain no reference to it.

We offer a solution that avoids these problems, at the price of limiting AspectJ's expressiveness. In particular, the usefulness for vastly crosscutting concerns such as logging and tracing is restricted. The solution is based on join points as instances of join point types exhibited by classes and advised by aspects, and polymorphic (i.e., per class) definitions of pointcuts. It achieves a form of decoupling that is comparable to that of event-driven programming, with remaining dependencies and corresponding interfaces as shown in the following figure.

Note that the pointcuts are now defined within the classes and thus the classes' implementation secrets. It follows that classes and aspects are fully encapsulated behind the join point type interface and can hence evolve independently. In other words: classes and aspects are now modular.

Worries that our suggestion leads to duplicate code, namely identical pointcuts scattered around a program, can be countered by letting classes refer to pointcuts of their superclasses using the keyword super. In any case, a class offering its join points for advising will know the pointcuts that specify them.

Top

Syntax and semantics of new language constructs

Construct Syntax Semantics
join point type [public|final|abstract] joinpointtype <name>
{<field declarations>}

A join point type is the intensional specification of a set of join points. It consists of a set of fields which bind to variables of the context in which a join point occurs. Its type predicate, a pointcut, is deferred to classes exhibiting join points of that type and is a disjunction of the branches defined therein. Fields may be declared as final to avoid reassignment.

Analogous to normal Java classes a joinpointtype can have different modifier. Thereby, the different modifier have the same meaning as they have for a class. Leaving the public modifier out means that the joinpointtype is only visible within its own package.

polymorphic pointcut pointcut <join point type> :
<AspectJ pointcut>;
A local branch (disjunct) of the pointcut (type predicate) associated with the join point type <join point type> whose scope is restricted to the class containing the polymorphic pointcut definition. The local pointcut maps the fields of <join point type> to variables used in the class definition (instance variables, method parameters etc.). The pointcut of <join point type> is then a disjunction of the local branches defined in various classes.
The pointcut of the same join point type of the superclass can be referenced using the keyword super.
join point exhibition class <class name> ... exhibits <join point type names>
{ ... }
A so-declared class announces that it exhibits (makes visible) join points of all types listed in <join point type names>. It must provide one local pointcut for each exhibited join point type.
join point advising aspect <aspect name> advises <join point types>
{... }
A so-declared aspect announces to advise join points of all types listed in <join point types>. It must provide one advice for each exhibited join point type; advice for join point supertypes counts as advice for its subtypes.
advice binding <kind> (<join point formal>) {<advice body>} Within an aspect, this binds a join point type to an advice body. <kind> is one of AspectJ's advice kinds such as before(), after(), around(), etc.
(see AspectJ documentation for detailed information). The formal parameter represents the join point instance within the advice. The fields of the join point can be accessed with <join point name>.<field name>.
calls to proceed proceed(<join point type instance>); In proceed calls the join point type instance is used as arguments to pass the contex information.
anonymous join point type exhibit new <join point type>(<parameters>)
{<statement>};
Creates an new join point instance of <join point type> for <statement>, where the actuals <parameters> are bound to the fields of the join point. The type predicate (pointcut) of this join point type, which is considered an anonymous subtype of <join point type>, remains implicit – it matches just this one statement.

The full syntax description in EBNF can be found here

Top

Conservative/Progressive

The compiler defines two modes. The two modes are called In the following the differences between the two modes are listed.
language construct conservative progressive
exhibit clause The exhibit clause (like in class A exhibits JP {...}) is not inherited to subclasses. The exhibit clause is inherited to subclasses.
polymorphic pointcuts Polymorphic pointcuts are not inherited to subclasses. Polymorphic pointcuts are inherited to subclasses, but the subclass can overwrite the pointcut declaration.

Implementation

We have implemented a compiler for our language as an extension to the AspectBench Compiler (abc). We have called our extension IIIA, which is short for Implicit Invocation with Implicit Announcement.

Download

An archive including the original abc-library, our IIIA extension for the abc, and a couple of examples is available from here. It unpacks to 2 new subdirectory named:

Notice:
The compiler needs a Java Runtime Environment (JRE) Version 1.5 or higher.
If you don't have a proper JRE installed, you can download it from http://java.sun.com.

Compile and run with Ant

To build and run our examples, we recommend to use Ant. An Ant script is included in the directory abc-ja-iiia-test with which the examples can be tried out easily. Simply type

ant -p

to get a list of available Ant targets. For instance, to build and run the shopping session example type:
ant run-example-buying

To use the progressive mode of the compiler you have to pass the VM-argument -Dmode=progressive to the compiler. This you can do by using the enviroment variable ANT_OPTS.

Compile and run with scripts

To use IIIA without Ant, or to compile and run your own code, we have included in the package two scripts for compiling and running:

To start the compiler, type in the directory abc-ja-iiia-test:

iiia-c <directory with source files> <output directory>

where the first directory defines the directory containing the source files (*.java) and the second directory defines the directory in which the compiler puts the compiled files (*.class). For example, to compile the buying example type:

iiia-c example-buying/src example-buying/bin

To run code building on IIIA, type

iiia <class file directory> <start class>

To execute the shopping session example, type:

iiia example-buying/bin application.Test

To invoke the compiler in the progressive mode the scripts include the environment JAVA_OPTS. This variable can be used to pass the VM-argument -Dmode=progressive to the compiler.

Compile and run without scripts

If you want to include additional libraries in your class path, you have to call the compiler manually. You can start the compiler by typing :

java -cp ../abc-ja-iiia-libs/abc-ja-complete.jar;../abc-ja-iiia-libs/abc-ja-iiia-jar;<your own jars> abc.main.Main -ext abc.iiia -sourceroots <source directory> -d <output directory>

Note that the class path entries are separated with ":" instead of ";" on *NIX systems.

In order to execute the code you have to make sure that the abc-runtime.jar is in your classpath (our abc-ja-iiia.jar is not necessary). To run the code you have to type :

java -cp ../abc-ja-iiia-libs/abc-runtime.jar;<directory with class files> <start class>

Refer to the abc documentation for further instructions on how to use abc.

In order to invoke the compiler in the progressive mode you have to pass the VM-argument -Dmode=progressive to the compiler.

Top

Known issues

  1. After advice cannot change variables in the context of a join point. This is a problem inherited from AspectJ (because proceed is the only way to affect context, and all changes to variables after a proceed have no effect on the context).
  2. An advice using the advice's implicit variables (like thisJoinPoint, thisJoinPointStaticPart, ...) but does not apply anywhere will cause an IndexOutOfBounceException. We have reported this issue to the abc-Team.
Top

Examples

The shopping session example

Our first example is typical for implicit invocation with implicit announcement (IIIA). It introduces it as a concept that is better known in the database community under the name of trigger (see also below).

The full example can be found here

joinpointtype CheckingOut

joinpointtype CheckingOut {
	// going to take this item and amount from stock
	Item item;
	int amount;
}

joinpointtype Buying

joinpointtype Buying extends CheckingOut {
	// buying this amount of item 
}

class ShoppingSession

class ShoppingSession exhibits CheckingOut,Buying {
	pointcut CheckingOut : call(* add(..)) && args(item, amount,..);
	pointcut Buying : call(* add(..)) && args(item, amount) && withincode(void buy(..));

	ShoppingCart sc = new ShoppingCart();
	Invoice inv = new Invoice();
	Log log = new Log("Shopping-Session-Log");
	Customer cus = customerLogOn();
	int totalAmount = 0;
	void buy(Item item, int amount) {
		System.out.println("ShoppingSession.buy("+item+","+amount+")");
	    sc.add(item, amount); // Matched by CheckingOut, Buying
	    inv.add(item, amount, cus); 
	    log.add(item, amount, cus); 
	    exhibit new Buying(item, amount) { // Matched by Buying
	      totalAmount += amount;
	    };
	    System.out.println("totalAmount="+totalAmount);
	}
	void rent(Item item, int amount, java.util.Date returndate) {
	    sc.add(item, amount); // Matched by CheckingOut
	    log.rent(item, amount, cus);
	}
	Customer customerLogOn() {return new Customer("Test "+System.currentTimeMillis());}
}

class ShoppingCart

class ShoppingCart /*exhibits Buying*/ {
//	  pointcut Buying : execution(* add(Item, int)) && args(item, amount);
	void add(Item it, int amount) {
		System.out.println("ShoppingCart.add("+it+","+amount+")");
	}
}

class Log

class Log /*exhibits Buying*/ {
//	pointcut Buying : execution(* add(Item, int, ..)) && args(item, amount, ..);
	String log;	
	Log(String log) {...}
	void add(Item item, int amount, Customer c) {...}
	void rent(Item item, int amount, Customer c) {...}
}

class Item

class Item {...}

class Invoice

class Invoice {...}

class Customer

class Customer {...}

aspect BusinessRules

aspect BusinessRules advises CheckingOut {
	before(CheckingOut co) {
		System.out.println("\t[check]\tCheckingOut : before "+thisJoinPoint+" -> "+co.getClass());
		System.out.println("\t[check]\tcheck if Stock is sufficient : Stock : "+Stock.amount(co.item)+" amount : "+co.amount);
		if (Stock.amount(co.item) < co.amount) {
			System.out.println("\t[check]\t-> Stock is not sufficient for Item : "+co.item);
			throw new OutOfStockException(co.item);
		}
	}
}

aspect BusinessRules

aspect BusinessRules advises CheckingOut {
	before(CheckingOut co) {
		System.out.println("\t[check]\tCheckingOut : before "+thisJoinPoint+" -> "+co.getClass());
		System.out.println("\t[check]\tcheck if Stock is sufficient : Stock : "+Stock.amount(co.item)+" amount : "+co.amount);
		if (Stock.amount(co.item) < co.amount) {
			System.out.println("\t[check]\t-> Stock is not sufficient for Item : "+co.item);
			throw new OutOfStockException(co.item);
		}
	}
}

aspect Buying

aspect BonusProgram advises CheckingOut, Buying {
	
	void around (CheckingOut jp) {
		System.out.println("\t** BonusProgram: around  -> "+jp.getClass());
		if (jp.item.category == Item.BOOK) {
			System.out.println("\t[buy]\t-> Item == BOOK : add "+jp.amount / 2+" to amount "+jp.amount);
			jp.amount += jp.amount / 2;
		}
		proceed(jp);
	}
	
	// This advice is no longer required as the around(CheckingOut) is applied on JP of Type Buying 
//	void around (Buying jp) {
//		...
//	}
}

The Telecom example from the AspectJ distribution

Join point types

First, the following join point types must be declared:

joinpointtype ConnectionComplete {
    Connection c;
}
joinpointtype ConnectionCreation {
    Customer cust;
}

joinpointtype ConnectionDropped {
    Connection conn;
}

For the rest, we show only the differences between the original program and that adapted to using join point types and polymorphic pointcuts. The complete example is in the our package.

Classes

original

public class Call {...

adapted

public class Call exhibits ConnectionComplete, ConnectionDropped, ConnectionCreation {
	pointcut ConnectionComplete : target(c) && call(void Connection.complete());
	pointcut ConnectionDropped : target(conn) && call(void Connection.drop());
	pointcut ConnectionCreation : args(cust, ..) && call(Connection+.new(..));

original

public abstract class Connection {...

adapted


public abstract class Connection exhibits ConnectionCreation {
	pointcut ConnectionCreation : args(cust, ..) && execution(new(..));

original

public class Local extends Connection {...

adapted

public class Local extends Connection exhibits ConnectionCreation {
	pointcut ConnectionCreation : super;

original

public class LongDistance extends Connection {

adapted

public class LongDistance extends Connection exhibits ConnectionCreation {
    pointcut ConnectionCreation : super;

Aspects

original

public aspect Billing {
	after(Customer cust) returning(Connection conn): args(cust, ..) && call(Connection+.new(..)) {...
	after(Connection conn): Timing.endTiming(conn) {...

adapted

public aspect Billing advises ConnectionDropped, ConnectionCreation {
	after(ConnectionCreation creation) returning(Connection c) {
    		c.payer = creation.cust;
    	}
	after(ConnectionDropped dropped) {
        	long time = Timing.aspectOf().getTimer(dropped.conn).getTime();
        	long rate = dropped.conn.callRate();
        	long cost = rate * time;
        	getPayer(dropped.conn).addCharge(cost);
    	}

original


public aspect Timing {
	after (Connection c): target(c) && call(void Connection.complete()) {...
	after(Connection c): endTiming(c) {...
	pointcut endTiming(Connection c): target(c) && call(void Connection.drop());

adapted

public aspect Timing advises ConnectionComplete, ConnectionDropped {
	after(ConnectionComplete complete) {
        	getTimer(complete.c).start();
    	}
    	after(ConnectionDropped dropped) {
        	getTimer(dropped.conn).stop();
        	dropped.conn.getCaller().totalConnectTime += getTimer(dropped.conn).getTime();
        	dropped.conn.getReceiver().totalConnectTime += getTimer(dropped.conn).getTime();
    	}
	// pointcut endTiming(Connection c): ...

That's all.

Top

Further examples

The following list presents further examples using IIIA:

Top

Related work

Conceptually, this work is a continuation of our prior work on type levels for AspectJ, to help avoid accidental recursion of aspects. The project is described here.

Top

Team

Friedrich Steimann, Thomas Pawlitzki, Sven Apel, Erich Kästner