ARJ: Extending AspectJ with Aspect Refinement and Mixin-Based Aspect Inheritance


Latest Version 0.2 (Feb 1, 2007)

Overview

ARJ integrates several novel language mechanisms into AspectJ:

Aspect Refinement

Refinements to existing aspects can be defined using the keyword refines. Aspects and their subsequent refinements are composed via mixin-based aspect inheritance. That means that the inheritance relationships and therewith the final order of the refinements is determined not until composition time. The following listing shows an aspect that synchronizess the access to Fifo buffers as well as a subsequent refinement that adds a field and a method.
aspect Sync {
  pointcut syncPC () : execution(* Fifo .*(..));
  Object around() : syncPC (){ /* synchronization code */ }
}
refines aspect Sync {
  int _threads /* thread counter */
  int threadCount () { /* getter for thread count */ }
}

Algebraic Equations for Compositsion

The ARJ compiler does not expect a set of source files but an equation file. Equation files where introduced by AHEAD, an architectural model for large-scale compositional and feature-oriented programming. Equations express which features (in or case aspects) contribute to a compound feature (aspect). Equation files simply enumerate the aspects and their refinements. Since refinements do not have an explicit name they have to be integrated into a separate directory. One aspect with n refinements results in n + 1 directories. Usually the directory name reflects the semantics of the particular refinements. This organization scheme is adopted from the AHEAD's containment hierarchies. The following code snippet shows how to invoke our ARJ compiler extension (abc.ar that build up on top of the abc). The only expected argument is an equation file that enumerates the input aspects and their refinements.
> abc.main.Main -ext abc.ar test.equation
An example equation file for a database tuning library is listed in the following code snippet:
Database
ConnectionPooling
MultiThreading
NetworkOptimizer

Pointcut Refinement

ARJ allows to refine aspects incrementally by adding and extending funtionality. Adding new constructs as methods, fields, advice, or pointcuts in refinements is straightforward. More interesting is the fact the existent structural elements, i.e. methods, pointcuts, advice, can be extended. Extending methods in ARJ is simimlar to classes:

aspect Sync {
  void lock () { /* lock access */ }
  void unlock () { /* unlock access */ }
}
refines aspect Sync {
  int threads ;
  void lock () { threads ++; super. lock (); }
  void unlock () { threads --; super. unlock (); }
  }
}

Besides this, extending pointcuts is useful to adjust the set of target join points via a refinement, i.e. extending or constraining the set of join points. In order to reuse the specification of the parent pointcut a programmer can use the keyword super:

aspect Sync {
  pointcut syncPC () : execution(* Fifo .*(..));
  Object around () : syncPC () { /* synchronization code */ }
}
refines aspect Sync {
  pointcut syncPC () : super.syncPC () || execution(* Stack .*(..));
}
Hanenberg et al. show that there are several useful applications of such pointcut refinement. They propose several design patterns that would profit of pointcut refinement, e.g. composite pointcut, pointcut method, template advice.

Advice Refinement

Before explaining advice refinement it is necessary to introduce the notion of named advice. In order to refine advice in subsequent development stages, they must be named first-class entities. Unfortunately, advice in AspectJ are explicit but cannot be referred from other entities, e.g. other advice or methods, by name or reference. In order to overcome this tension ARJ provides named advice. Named advice are named first-class entities of aspects. They can be overridden and referred to from a child advice in order to refine its functionality. This enables the programmer to reuse and evolve advice over several development stages. Having named advice all structural elements of aspects can be refined in the same way (by using the super keyword). The following code snippet lists a synchronization aspect that contains a named advice. Compared to native AspectJ advice, named advice declare a name (syncAdvice)

aspect Sync {
  pointcut syncPC () : execution(* Fifo .*(..));
  Object around syncAdvice () : syncPC () { /* synchronization code */ }
}
Although, there is also an alternative way to express named advice, we omit an explaination and refer to our technical paper that analyzes this in more depth. By introducing named advice to AspectJ we can refine advice of parent aspects. The bottom listing depicts an aspect that refines our synchronization aspect by extending its named advice, i.e. its named advice syncAdvice is overridden. In the refining aspect the named advice is treated as a method. The signature and name can simply be infered from the parent named advice. Since a named advice can be treated as as method it can be extended as it would be a method. As with common method refinement refining advice may use the super keyword in order to invoke the parent advice. Notice that always the most refined advice is invoked by an bound pointcut.
refines aspect Sync {
  Object syncAdvice () {
    /* additional advice code */
    return super. syncMethod ();
  }
}

Complete Example

The following example shows an aspect and an aspect refinement that make use of all novel language features of AJR.

aspect Sync {
  void lock () { /* lock access */ }
  void unlock () { /* unlock access */ }
  pointcut syncPC () : execution(* Fifo .*(..));
  Object around syncMethod () : syncPC () { /* synchronization code */ }
}
refines aspect Sync {
  int threads ;
  void lock () { threads ++; super. lock (); }
  void unlock () { threads --; super. unlock (); }
  pointcut syncPC () : super. syncPC () || execution(* Stack .*(..));
  Object syncMethod () {
    /* advice code */
    return super. syncMethod ();
  }
}