Section 8.3. THE CAESAR MODEL


8.3. THE CAESAR MODEL

A core feature of CAESAR is the notion of an aspect collaboration interface (ACI for short)an interface definition for aspects with multiple mutually recursive nested types. The purpose of an ACI is the decoupling of aspect implementations and aspect bindings, which are defined in independent but indirectly connected modules. The idea is that while being independent of each other, these modules implement disjoint parts of a common ACI, which indirectly relates them as parts of a whole. We illustrate our ideas also by means of the observer example. Listings 8-3, 8-4, and 8-5 show an ACI for the observer protocol, an aspect implementation, and an aspect binding, respectively. We discuss each in turn.

Listing 8-3. ACI for observer protocol
 interface ObserverProtocol {    interface Subject {      provided void addObserver(Observer o);      provided void removeObserver(Observer o);      provided void changed();      expected String getState();    }    interface Observer { expected void notify(Subject s); } } 

Listing 8-4. Sample impl. of observer protocol
 class ObserverProtocolImpl implements ObserverProtocol {    class Subject {      List observers = new LinkedList();      void addObserver(Observer o) { observers.add(o);}      void removeObserver(Observer o) {         observers.remove(o);      }      void changed() {        Iterator it = observers.iterator();        while ( iter.hasNext() )          ((Observer)iter.next()).notify(this);      }    } } 

Listing 8-5. Sample binding of observer protocol
 class ColorObserver binds ObserverProtocol {   class PointSubject binds Subject wraps Point {     String getState() {       return "Point colored "+wrappee.getColor();     }   }   class LineSubject binds Subject wraps Line {     String getState() {       return "Line colored "+wrappee.getColor();     }   }   class ScreenObserver binds Observer wraps Screen {     void notify(Subject s) {       wrappee.display("Color changed: "+s.getState());     }   }   after(Point p): (call(void p.setColor(Color)))       { PointSubject(p).changed(); }   after(Line l): (call(void l.setColor(Color)))       { LineSubject(l).changed(); } } 

8.3.1. Aspect Collaboration Interfaces

An ACI consists, in general, of several mutually recursive nested ACIs, one for each abstraction in the modular structure of the aspect. The ACI ObserverProtocol in Listing 8-3, for example, has two nested ACIs, Subject and Observer, that are mutually recursive in that the name of each type is used in the definition of the other. A simple ACI which does not contain other nested ACIs (e.g., Subject) is a special kind of interface. It lays down a bidirectional communication protocol between any possible implementation and binding of the corresponding abstraction. It does so by distinguishing between two part-interfaces: the provided and the expected facets of the abstraction, consisting of methods declared with the modifiers provided and expected, respectively. Hence, we can redefine an ACI as consisting of expected and provided declarations for the aspect as a whole as well as a set of mutually recursive nested ACIs, one for each abstraction in the modular structure of the aspect. The provided facet of an aspect lays down what the aspect provides to any context in which it is applied. The observer ACI in Listing 8-3 specifies that any implementation of ObserverProtocol must provide an implementation of the three provided methods of Subject.[3] On the other hand, the expected facet of an aspect makes explicit what the aspect requires of the context in which it is applied in order to be able to supply what the provided facet promises. Hence, the expected facet declares methods whose implementation is binding-specific.

[3] In this example, the Observer abstraction does not have any provided methods. However, one can easily think of other examples where more than one abstraction declares a non-empty provided facet.

Consider for instance the part of the observer protocol concerned with communicating relevant state from the subject to observers on change notification. The relevant part of the subject's state and how this state should be extracted are highly dependent on what classes play the subject and observer roles in a particular composition. Furthermore, the operation to be called on the observer as part of the notification is also binding-specific. This is why notify() and getState() are declared with the modifier expected in Listing 8-3.

An ACI's provided and expected facets are implemented in different modules, called aspect implementations and aspect bindings respectively. However, all implementations and bindings of the same ACI are indirectly connected to each other since they implement two facets of the same whole. The common ACI serves as a medium for bidirectional communication between them: Any module that implements one of the facets can freely use declarations in the other facet. This loose coupling is the key to independent reuse of implementations and bindings.

8.3.2. Aspect Implementations

An aspect implementation must implement all methods in the provided facet of the corresponding ACI, i.e., all aspect-level provided methods, as well as provided facets of all nested ACIs. Listing 8-4 shows a simple implementation of the ObserverProtocol ACI. Similarly, we could write another implementation of ObserverProtocol, say, a class AsyncObserverImpl that implements ObserverProtocol and that realizes a notification strategy with asynchronous updates.

As illustrated in Listing 8-4, an aspect implementation is a class that declares itself with an implements clause. Provided facets of the nested ACIs are implemented in nested classes that have the same names as their respective nested ACIs (see ObserverProtocolImpl.Subject in Listing 8-4). The implementation of provided methods can call expected methods of the same or of other abstractions of the same aspect. For example, Subject.changed() calls notify(), which is declared in the expected facet of ObserverProtocol.Observer. Nested implementation classes are free to define additional state and behavior (for example, as the observers field in Subject). Since ObserverProtocol.Observer has no provided methods, there is no Observer class in Listing 8-4, but we could have added additional state and behavior with Observer, if necessary.

8.3.3. Aspect Bindings

An aspect binding implements all expected methods in the aspect's CI and in its nested interfaces. Listing 8-5 shows a binding of ObserverProtocol, which maps the subject role to Point and Line and the observer role to Screen. The class ColorObserver declares itself as a binding of ObserverProtocol by means of a binds clause.

For each nested ACI of ObserverProtocol, i.e., Subject and Observer, there might be zero, one, or more nested bindings inside ColorObserver. The latter are also declared with a binds clause and must implement all expected methods in the corresponding interface. The relation between nested types in an ACI and their binding classes is not established by name identity since there might be more than one binding for the same abstraction within the same binding class, as in Listing 8-5.

Aspect binding is almost pure OO: A binding class refers to one or more base objects and uses their interface for implementing the expected facet of the aspect abstraction. The aspect binding in Listing 8-5 uses only three non-OO features: (a) the wrap clause and the wrappee keyword, (b) wrapper recycling and (c) pointcuts and advices. Features (b) and (c) are explained in Sections 8.3.4 and 8.3.6. The wrap clause and the keyword wrappee are syntactic sugar for the common case, when each aspect abstraction is mapped to exactly one base class. For example, class PointSubject binds Subject wraps Point ... is syntactic sugar for:

 class PointSubject binds Subject {   Point wrappee;   PointSubject(Point wrappee) { this.wrappee = wrappee; }   ... } 

In general, a wrapper class may have an arbitrary number of "wrappees" that can be initialized or computed in the constructor.

Due to bindings being almost pure OO in CAESAR, the programmer can encode more complicated cases, where the relation to application objects has to be computed or is represented by multiple application objects. Consider, for example, an observer that is to be notified when the coordinates of points p1 and p2 are changed, reducing their distance to less than some d. In this case, there is no direct mapping from the subject role to any application classthe subject role is represented by a pair of point objects.

8.3.4. Wrapper Recycling

A subtle issue when using wrappers is avoiding creating multiple wrappers for the same base object (what we have called wrapper identity hell [19]). Our solution is a mechanism called wrapper recycling. Syntactically, wrapper recycling refers to not simply creating an instance of a wrapper W with a standard new W(constructorargs) constructor call but instead retrieving a wrapper with the construct outerClassInstance.W(constructorargs). For illustration, consider the expressions PointSubject(p) and LineSubject(l)[4] in the after-advice in Listing 8-5. We use the usual Java scoping rules. For example, PointSubject(p) is just an abbreviation for this.PointSubject(p).

[4] Recall that the clauses wraps Point and wraps Line imply corresponding constructors.

The semantics of wrapper recycling is that it guarantees a unique wrapper for every (set of) wrappees in the context of an outerClassInstance. This is due to the semantics of a wrapper recycling call, which caches nested wrappers. The outer class instance maintains a map mapW for each nested wrapper class W. An expression outerClassInstance.W(wrapperargs) corresponds to the following sequence of actions:

1.

Create a compound key for the constructor arguments and look up this key in mapW.

2.

If the lookup for the key fails, create an instance of outerClassInstance.W with the annotated constructor arguments, store it in the hash table mapW, and return the new instance. Otherwise, return the object already stored in mapW for the key.

Hence, the call to the wrapper recycling operation PointSubject(p) is equivalent to the corresponding constructor call only if a wrapper for p does not already exist. That is, two subsequent wrapper retrievals for a point yield the same PointSubject instance. The identity and state of the wrapper are preserved.

A naïve implementation of wrapper recycling in a language with garbage collection would imply a memory leak because wrapped objects would never be collected by the garbage collector. However, this can easily be reconciled by more advanced memory management techniques such as weak references and reference queues. Java, for example, has a standard API class WeakHashMap that could be used instead of a usual map. The discussion about what technique to use concerns the implementation of a compiler (runtime system) for CAESAR. It remains transparent to the programmer, who works with the concept of wrapper recycling rather than with low-level realization details of this concept. This is different from the use of the WeakHashMap data structure in the AspectJ implementation of the observer protocol discussed in Section 8.2.

8.3.5. Most Specific Wrappers

Another interesting feature of CAESAR is its notion of most specific wrappers: A mechanism that determines the most specific wrapper for an object based on the object's run-time type when multiple nested binding classes with the same name are available. Consider, for example, MovableFigures in Listing 8-6, which contains three nested classes named MovableFigure. These classes have different constructors, though. (Recall that the wraps clause is just syntactic sugar for a corresponding constructor.) On a constructor or wrapper recycling call, the dynamic type of the argument determines the actual nested binding to instantiate/recycle. For example, if Test.move(Figure) in Listing 8-6 is called with a Point as the actual parameter f, the wrapper recycling call mv.MovableFigure(f) returns an instance of the MovableFigure implementation that wraps Point.

Listing 8-6. Using most specific wrappers
 class MovableFigures {   class MovableFigure implements Movable wraps Figure {     void moveBy(int x, int y) {};   }   class MovableFigure implements Movable wraps Point {     void moveBy(int x, int y) {       wrappee.setX(wrappee.getX()+x);       wrappee.setY(wrappee.getY()+y);     }   }   class MovableFigure implements Movable wraps Line {     void moveBy(int x, int y) {       MovableFigure(wrappee.getP1()).moveBy(x,y);       MovableFigure(wrappee.getP2()).moveBy(x,y);         }   } } class Test {   MovableFigures mv = new MovableFigures();   void move(Figure f) {     mv.MovableFigure(f).moveBy(5,7);   } 

The mechanism of most specific wrapper is very similar to multiple dispatch in languages such as CLOS, Cecil [5], or MultiJava [6]. More precisely, if one thinks of the constructors of nested classes as factory methods of the enclosing instance, then our mechanism is an application of multiple dispatch at these factory methods.

8.3.6. Pointcuts and Advices

As illustrated in Listing 8-5, CAESAR also has advices and pointcuts, which while being similar to AspectJ differ from it in two ways. The first concerns the decoration of executing (target) objects at a join point with aspect types. This decoration is implicit in AspectJ. For example, consider the pointcut subjectChange in Listing 8-2: The base object, s, brought into the scope of ColorObserver by the join point target, whose type is either Line or Point, is automatically seen as being of type Subject within ColorObserver (as per the parameter type of the pointcut).

In CAESAR, the conversion is explicit via wrapper recycling calls. In Listing 8-5, we avoided type conversions in a pointcut in order to avoid mingling the discussion on wrapper recycling with that of pointcuts and advices. For this reason, we defined different pointcuts for Point and Line. A shorter variant of the same binding, where we use conversions in the pointcuts, is given in Listing 8-7. Note that the explicit calls to wrapper recycling operators within the with clauses in Listing 8-7 allow us to decorate basis objects with different aspect facets in each "case" of the pointcut. We prefer the explicit variant, because it increases the programmer's expressiveness: The programmer can choose among several constructors of the binding classes, if more than one is available. For instance, we could create an observer binding that reacts on both color changes and coordinate changes. In the case of coordinate changes, the getState() method should give us information about the new coordinates and not about the color as in Listing 8-5. In this case, we could add another Point binding PointSubject2 to the code in Listing 8-5 that has a different implementation of getState() and select the PointSubject2 binding in the respective case (coordinate changes) of the pointcut definition.

Listing 8-7. Alternative binding of observer
 public class ColorObserver binds ObserverProtocol {     ... as before ...   after(Subject s):     ( call(void Point.setColor(Color))          with s = PointSubject(target)) ||     ( call(void Line.setColor(Color))           with s = LineSubject(target) ) {     s.changed();   } } 

The second and more important difference between CAESAR and AspectJ pointcuts and advices is at the semantic level. Compiling a binding class that contains advice definitions does not have any effect on the base application's semantics. This is because an aspect (its implementation and binding) must be explicitly deployed in CAESAR. Only the advice definitions of explicitly deployed aspects are executed, as we discuss in the next section.

8.3.7. Weavelets and Deployment

In order to gain a complete realization of an aspect type, an implementation-binding pair needs to be composed into a new unit called a weavelet. An example of a weave let is the class CO in Listing 8-8, which represents a complete realization of the ObserverProtocol interface that combines the implementation ObserverProtocolImpl with the binding ColorObserver, denoted by the declaration after the extends clause.

Listing 8-8. Weavelet composition
 class CO extends   ObserverProtocol<ColorObserver,ObserverProtocolImpl> {}; 

A weavelet is a new class within which the respective implementations of the expected and provided methods from the binding and implementation parts are composed. The composition takes place recursively for the nested classes; all nested classes with a binds declaration are combined with the corresponding implementation from the implementation class.

A weavelet has to be deployed in order to activate its pointcuts and advices. A weavelet deployment is syntactically denoted by the modifier deploy and comprises basically two steps: (a) create an instance of the weavelet at hand and (b) call the deploy operation on it. One can choose between static (load time) and dynamic deployment.

8.3.7.1 Static Deployment

Static deployment is expressed by using the deploy keyword as a modifier of a final static field declaration. Semantically, it means that the advices and pointcuts in the instance that has been assigned to the field become active. For example, co is deployed when Test is loaded in the following code extract:

 class Test ... {   deploy public static final CO co = new CO();   ... } 

The object assigned to co could also be computed in a static method; hence, the weavelet that is actually deployed might also be a subtype of CO, thereby enabling static aspectual polymorphism. The deploy keyword can also be used as a class modifier. This variant should be regarded syntactic sugar in the sense that deploy class CO ... {... } is equivalent to declaring a deployed field named THIS as in:

 class CO ... {    deploy public static final CO THIS = new CO();    ... } 

Listing 8-9 shows the declaration of a statically deployed color observer protocol together with sample code that shows how the deployed weavelet instance can be accessed (register()). Since CO.THIS is deployed, the pointcuts of the observer protocol are active, i.e., color changes in points and lines are propagated to CO.THIS.

Listing 8-9. Static aspect deployment
 deploy class CO extends   ObserverProtocol<ColorObserver,ObserverProtocolImpl>{}; ... void register(Point p, Screen s) {   CO.THIS.PointSubject(p).addObserver(    CO.THIS.ScreenObserver(s)); } 

Using deploy as a class modifier is appropriate if we need only one instance of the aspect and if aspectual polymorphism is not required. By means of deploy as a field modifier, we can create and deploy multiple instances of the same weavelet and select from different weavelets using aspectual polymorphism. Having two instances of, say, the CO weavelet in the observer example would mean that every Point and Line would have two independent facets as subject with independent lists of observers. An example that makes more sense is the association of color to elements of a data structure, which can be seen as nodes of a graph. Multiple independent instances of the corresponding weavelet would represent multiple independent colorings of the graph. Other examples can be derived from role modeling, where frequently one object has to play the same role twice. An example of this situation is when a person is an employee in two independent companies. Static aspectual polymorphism is useful if we want to select a particular weavelet based on conditions that are known at load-time. For example, based on the number of processors or the multi-threading support, one might either choose a usual observer pattern implementation or one with asynchronous updates.

8.3.7.2 Dynamic Deployment

Dynamic deployment is denoted by the keyword deploy used as a block statement. The rationale behind dynamic deployment is that frequently we cannot determine which variant of an aspect should be applied (or whether we need the aspect at all) until runtime. Consider, for example, a program with different logging options, i.e., without logging, with standard logging, and with "verbose" logging. In CAESAR, this can be implemented as in Listing 8-10.[5] We have two different logging aspects related by inheritance, Logging and VerBoseLogging, and we choose one of them at runtime, depending on the command line arguments with which the program has been started.

[5] In order to keep the example simple, we do not use separate binding and implementation here. If separation of implementation and binding would be overkill, we can collapse both parts into a single unit.

Listing 8-10. Polymorphic aspect deployment
 class Logging {   after(): (call(void Point.setX(int)) ||     call(void Point.setY(int)) ) {     System.out.println("Coordinates changed");   } } class VerboseLogging extends Logging {   after(): (call(void Point.setColor(Color)) {      System.out.println("Color changed");   } } class Main {   public static void main(String args[]) {     Logging l = null;     Point p[] = createSamplePoints();     if (args[0].equals("-log"))       l = new Logging();     else if (args[0].equals("-verbose"))       l = new VerboseLogging();     deploy (l) { modify(p); }   }   public static void modify(Point p[]) {     p[3].setX(5);     p[2].setColor(Color.RED);   } } 

The deploy block statement in Main.main specifies that the advices defined in the annotated aspect instance l become active in the control flow of the deploy block; in this case, during the execution of modify(f). In particular, other independent threads that execute the same code are not affected by the deploy clause. The advice and pointcuts that will be activated in the deploy block are not statically known; l is only known by its upper bound Logging (l could have also been passed as a parameter). In other words, the advices are late-bound, similarly to late method binding; hence our term aspectual polymorphism. If l is null, the deploy clause has no effect at all.

The usefulness of dynamic deployment becomes clear when we consider a "simulation" of this functionality by means of static deployment. With static deployment, we would have to encode the different variants by conditional logic in the aspect code.[6] The structure of the aspect would be awkward, because all variants of the aspect are tangled inside a single aspect module. In a way, this is similar to simulating late binding in a non-OO language. Dynamic aspectual polymorphism can be seen as an imperative consequence of integrating aspects into the OO concept world. Without aspectual polymorphism, programs would also be fragile with respect to concurrent programs; additional synchronization measures would be required.

[6] Our example also uses conditional logic in Main.main. However, we select the logging variant once and never have to do any checks again (a factory object could have been used, as well), whereas without dynamic deployment, the check would be redone at every joinpoint.

An interesting question is whether the aspect deployment code should also be separated from the rest of the code. This can easily be done with another aspect whose responsibility is the deployment of the logging aspect, as illustrated in Listing 8-11. In this code, the aspect LoggingDeployment (which is itself deployed statically) computes and deploys an appropriate logging aspect by means of an around advice; i.e., the proceed() call is executed in the context of the logging aspect.

Listing 8-11. Aspect deployment aspects
 deploy class LoggingDeployment {   around(final String s[]): cflow(Main.main(String[])       && args(s)  && (call(void Main.modify(Point[])) {     Logging l = null;     if (...) l = new Logging(); else ... ;     deploy (l) in { proceed(s); }   } } class Main {   public static void main(String args[]) {     Point p[] = createSamplePoints();     modify(p);   }   public static void modify(Point p[]) {...} } 

8.3.8. Virtual Classes and Static Typing

In CAESAR, all nested interfaces of a CI and all classes that implement or bind such interfaces are virtual types/classes, as in the family polymorphism approach [9]. Similar to fields and methods, virtual types also become properties of objects of the class in which they are defined. Hence, their denotation can only be dynamically determined in the context of an instance of the enclosing class. The rationale behind using family polymorphism lies in its power with respect to reuse and polymorphism at the level of multiple related abstractions.

If we want to have a variant of a binding, weavelet, or CI, we can refine the respective entity by creating an extension within which the nested virtual types/classes can be overridden. LazyColorObserver in Listing 8-12 refines the behavior of ColorObserver in Listing 8-5 by using virtual class overriding (declared with the keyword override)a lazy ScreenObserver reacts only after being notified ten times about a change. The important observation to make here is that even if the definition of PointSubject and LineSubject are inherited unchanged, references to Observer within their respective implementations are automatically bound to LazyColorObserver.ScreenObserver during the execution of any method on an instance of LazyColorObserver.

Listing 8-12. Lazy color observer
 public class LazyColorObserver extends ColorObserver {   override class ScreenObserver {     int count = 0;     void notify(Subject s) {       count++;       if (count >= 10) { super.notify(s); count = 0; }     }   } } 

However, this flexibility is not paid for with loss of static typing: An improvement of the type system proposed in [9] preserves the ability to detect type errors at compile time. The integration of virtual classes [15] and family polymorphism [9] with collaboration interfaces and their implementation and binding units has already been described in [19].



Aspect-Oriented Software Development
Aspect-Oriented Software Development with Use Cases
ISBN: 0321268881
EAN: 2147483647
Year: 2003
Pages: 307

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net