In this section we show by example how programming with abstract slices works.
The crosscutting described in this section is related to object slicing. The object behavior is expressed in terms of a set of object slices. A slice of an object is a subobject. The problem with object slices is that they are not robust under changes to the class graph. Therefore it is useful to program methods abstractly in terms of object slices and to give the details of the slices succinctly only when the method is called. This leads to more reusable methods.
A simple example where we can see the benefit of object slicing is counting. The following Java code counts WhatToCount objects reachable through the whereToCount object slice (for class Utility).
static public int count(ObjectGraphSlice whereToCount){ Integer result = (Integer) whereToCount.traverse( new Visitor() { int c; public void start() { c=0; } public void before(WhatToCount o) { c++;} public Object getReturnValue() {return new Integer(c);} }); return result.intValue(); }
The counting behavior is naturally formulated with two participants: Source and Target. When the counting behavior is used, we can map Source to BusRoute and Target to Person, for example. We also need method-valued variables. Consider a summing behavior (instead of just a counting behavior). When we arrive at a target, we need to call a function to return the value to be summed. This function needs to be variable.
We show how traversal strategies are planned to be used in adapters. If we want to count the number of persons waiting at some bus stop in a bus simulation system, we would write the following Java code:
ClassGraph cg = new ClassGraph(); // constructed using reflection ObjectGraph og = new ObjectGraph(aBusRoute,cg); int res = Utility.count(new ObjectGraphSlice(og, "from BusRoute through BusStop to Person"));
The above examples that we are using are written in Java using a library called DJ that we have developed recently. The string "from BusRoute through BusStop to Person" is parsed by the constructor.
The object slice is prepared depending on the structure of the current class graph. We assume that Person has been substituted for WhatToCount in method count. Notice that the implementation to object slicing shown here is not type-safe because the compiler does not check whether the navigation is meaningful in the current class graph. To add type safety to this implementation is future work.
Using the terminology of AspectJ, an object slice is a pointcut consisting of all entry and exit events for the objects through which the slice leads us. In AspectJ, the code that needs to be executed when the events happen is specified as advice. AspectJ also uses the notion of abstract pointcut. Pointcuts are refined through subclassing.
We notice the following correspondence between AspectJ concepts and slicing concepts:
AspectJ pointcut advice abstract pointcut concrete pointcut Slicing object slice visitor formal object slice actual object slice
From a software architecture perspective, object slicing uses methods parameterized by object slices as components and the connectors are the actual object slice definitions that connect the abstract behavior to an actual class graph. The connectors are expressed in terms of traversal strategies, a central concept in adaptive programming.
The counting example is very simple and next we express the idea of programming with object slices with a more complex example. The terminal buffer rule (TBR) says that all terminal classes should be ``buffered" by a class that has the terminal class as part class. TBR improves readability of class graphs. To implement the terminal buffer rule, we need to assume a structure for representing classes, e.g., the UML meta model. We assume that there is a class CdGraph for which we write the following code that demonstrates that a severely crosscutting concern can still be expressible using a generalized procedure. This is not too surprising because this example makes heavy use of reflective capabilities through the use of DJ.
Although the following code is not an aspect per se, it has the characteristics of an aspect in AspectJ: The two traversal definitions are pointcuts and the visitors are the advice.
public void TBRchecker( TraversalGraph defineClassNameTraversal, TraversalGraph allPartsTraversal) { // defineClassNameTraversal defines the part of the object graph // that is relevant for finding all defined classes. // allPartsTraversal defines the part of the object graph // that is relevant for checking the TerminalBufferRule // find defined classes DefinedClassVisitor v1 = new DefinedClassVisitor(); Vector definedClasses = (Vector) defineClassNameTraversal.traverse( this, v1); // check for violations TBRVisitor v2 = new TBRVisitor(definedClasses); allPartsTraversal.traverse(this, v2); }
We have chosen to parameterize the TBRchecker method with two TraversalGraph-objects instead of with ObjectGraphSlice-objects. The reason is that it is more natural to compute the slices internal to the method from the TraversalGraph-objects. This happens implicitly when the traverse method is executed. Method TBRchecker makes very few assumptions about the class graph structure. Those assumptions are (1) there exists one slice to find all defined classes and (2) there exists one slice to find all part classes. In addition, there are assumptions encoded into the visitors. We need two visitors, one of which is shown here:
public class DefinedClassVisitor extends Visitor { private Vector vNonTerminals = new Vector(); public void before(Adj o) { idCurrentAdj = o.vertex.name; vNonTerminals.addElement(idCurrentAdj);} public Object getReturnValue() { return vNonTerminals; } }
Visitor DefineClassVisitor expects that the slice defineClassNameSlice goes through class Adj that must have a part vertex.
For a concrete use of TBRchecker we get:
ClassGraph cg = new ClassGraph(); // constructed by reflection // The purpose of traversal tg1 is to collect all the class names that // are defined in the model TraversalGraph tg1 = new TraversalGraph( "from Cd_graph to Adj", cg); // The purpose of traversal tg2 is to visit all parts of all classes TraversalGraph tg2 = new TraversalGraph( "from Cd_graph via Construct to Vertex", cg); CdGraph cdGraph = new CdGraph(...); cd_graph.TBRchecker(tg1,tg2);
The above code is adaptation code that defines two concrete traversal graph objects for the current class graph and object graph.
Notice that TBRchecker is a very generic checker that can be applied to many class graphs that satisfy certain constraints. Analyzing this constraint space is reserved for future research.
It is important to mention that there is a subtle difference between the slice-based programming style shown here and the adaptive programming styles used in Demeter/C++ or in DemeterJ. In DemeterJ we consider the traversal strategies as an integral part of the generic program. This ``tradition" also lives on in AP&PC where the participant graph is an integral part of the program. With the slice-based approach, the traversal strategies are delegated outside the generic behavior which is formulated in terms of ``abstract" slices. The slice-based approach has the important advantage of simplicity and more genericity. It is clear that collaborations written using abstract slicing will have import methods that return object slices or traversal graphs. But it is not yet completely clear how to express the requirements that the actual slices or traversal graphs need to fulfill.
After exposition by example of the abstract slicing programming style and its relationship to AspectJ, we give a more complete definition of the concepts involved. We need the concepts of ClassGraph, ObjectGraph, Strategy, TraversalGraph, ObjectGraphSlice and Visitor. ClassGraph and ObjectGraph are familiar concepts, e.g. also used in UML. Objectgraph contains both an object called root and a class graph. A Strategy is basically a subgraph of the transitive closure of the class graph decorated with negative information about which nodes and edges to bypass. A Strategy also defines a set of source and target classes where traversals start and stop. ATraversalGraph is basically the crossproduct of a ClassGraph and a Strategy. Traversal graphs are used to guide a traversal efficiently through an object following the rules of a strategy. An ObjectGraphSlice consists of an ObjectGraph and a Strategy.