Crosscutting in Aspect-Oriented Programming While AOP is based on the premise that crosscutting is important, the current implementation in AspectJ only addresses a certain kind of cross-cutting. In this section we show a kind of crosscutting that is very common in object-oriented systems and that should be handled safely by an aspect-oriented programming language. The example, that we are using is written in Java, using a library called DJ that we have developed recently. The crosscutting that we address here is related to object slicing: a behavior that we need for an object is expressed in terms of a set of object slices of the object and maybe other objects. A slice of an object is a path set starting at the root of the object. Path sets are defined in [TOPLAS] and it is important to remember that an object slice is in general more complex than 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 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. Consider the following Java code: 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(); } It says that we want to count objects reachable through object slice whereToCount and that we want to count WhatToCount objects. 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: /course/com3362/sp99/DJ/bus-with-villages ClassGraph cg = new ClassGraph(); // constructed by reflection ObjectGraph og = new ObjectGraph(aBusRoute,cg); int res = aBusRoute.count(new ObjectGraphSlice(og, "from BusRoute through BusStop to Person")); 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 naviagtion 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 that is 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 we would like to 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. The complete terminal buffer rule example is at: http://www.ccs.neu.edu/research/demeter//DJ/annot-examples/TBR/TBR1-flexible 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 ClassGraph for which we write the following code: public void TBRchecker( ObjectGraphSlice defineClassNameSlice, ObjectGraphSlice allPartsSlice) { // defineClassNameSlice defines the part of the object graph // that is relevant for finding all defined classes. // allPartsSlice defines the part of the object graph // that is relevant for checking the TerminalBufferRule // find defined classes DefinedClassVisitor v1 = new DefinedClassVisitor(); Vector definedClasses = (Vector) defineClassNameSlice.traverse(v1); // check for violations TBRVisitor v2 = new TBRVisitor(definedClasses); allPartsSlice.traverse(v2); } This method makes very few assumptions about the class graph structure. Those assumptions are that there exists one slice to find all defined classes and that 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) { Ident idCurrentAdj; idCurrentAdj = o.vertex.name; System.out.println("Adj: " + idCurrentAdj.toString()); 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 applied to object cd_graph we get: ClassGraph cg = new ClassGraph(); // constructed by reflection ObjectGraph og = new ObjectGraph(cd_graph,cg); cd_graph.TBRchecker2( new ObjectGraphSlice(og,"from Cd_graph to Adj"), new ObjectGraphSlice(og,"from Cd_graph via Construct to Vertex")); The above code is adaptation code that defines two concrete object graph slices for the current class graph and object graph. Class ObjectGraph should be considered as a helper class, that lifts a Java object into a space that allows computation of object graph slices. 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. To summarize the slicing programming style, we use the following pattern: Pattern: Abstract Slicing Intent: Write algorithms at a high level of abstraction. Motivation: The AP motivation: Avoid polluting algorithms with accidental details yet write them so that they can handle all that accidental detail without modification. Applicability: Applications that involve multiple connected objects that need to be visited. Structure: Parameterize methods with abstract object graph slices that are needed to express the methods abstractly. It might be necessary to also pass slices as arguments to visitors that need to do subtraversals. Related idea: AspectJ supports the notion of an abstract point cut. Type ObjectGraphSlice is a kind of abstract point cut. Implementation: DJ provides a good implementation of abstract object slices but does not support static checking. That will be added as a separate tool. Tradeoff: use of the pattern makes algorithms more generic but each use of the algorithm requires object graph slice preparation. The input object needs to be prepared (adapted) for processing based on the details of the class graph. end of pattern After exposition by example of the slicing programming style and its relationship to AspectJ, we give a more complete definition of the cooncepts involved. We need the concepts of ClassGraph, ObjectGraph, Strategy, 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 [strategies paper]. A strategy also defines a set of source and target classes where traversals start and stop. An ObjectGraphSlice consists of an ObjectGraph and a strategy. An important auxiliary concept is a traversal graph which is basically the crossproduct of a class graph and a strategy. Traversal graphs are used to guide a traversal efficiently through an object following the rules of a strategy. The slicing programming style shows the need for further parameterization of programs. Generic behavior should be expressed in terms of class-valued variables that we call participants. The counting behavior is naturally formulated with two participants: Source and Target. When the counting behavior is used, we can map Source to ClassGraph and Target to Vertex, for example. Each participant has import/export and importexport methods. import: object slices export: object behavior Using import allows us to remove the slices from the parameter list Why good? motivate all other features