• No results found

Control Flow-Based Designators

In our examples so far, we have allowed join points to be matched throughout an application without regard to when the join point originated. For example, we were able to catch all calls to the setLocation() method made on a DVD object. The setLocation() method is an accessor function for the Location attribute, and it is defined as public. This means that it can be called by anyone. Looking at the DVD class code at the beginning of this chapter, we can see that the setLocation() method is also called within the setStats() method. It would be interesting if we could localize the matching of the setLocation() method to just the call within the setStats() method. This just so happens to be what the cflow and cflowbelow designators allow. The format of these designators is

cflow(pointcut) cflowbelow(pointcut)

These are the first designators where neither an ID nor a join point signature is used as a parameter. Instead, the parameter to these designators is a pointcut definition. This is because these designators come into play when a specific pointcut has been matched. They act as a sort of flag to the system, telling it when a pointcut has been reached and when the pointcut is no longer valid.

cflow

The cflow designator matches any join points that occur beginning when a call to a specific method takes place until the end of the method. Let’s look at an example using the DVD class:

pointcut setLoc() :

call(public void DVD.setLocation(..)); pointcut setStat() :

call(public void DVD.setStats(String, int)); pointcut localSetLoc() :

setLoc() && cflow(setStat());

before() : localSetLoc() {

System.out.println(thisJoinPoint.toLongString()); }

The first two pointcuts match join points on the two methods in which we are interested. The setLoc() pointcut represents the call to the setLocation() method, which is our primary focus. The second pointcut, setStat(), is the method that contains a setLocation() call. The use of separate pointcuts for these join points is only partially for convenience. Recall that the cflow desig- nator only takes a pointcut as a parameter. The pointcut can be a named point- cut, as in our earlier example, or just the designator, like cflow(call(public void DVD.setLocation(..));.

The third pointcut in this example combines two conditions. The first is the set- Loc() pointcut, which indicates that the setLocation() join point must be matched for this pointcut to be matched. The second condition is the cflow des- ignator using a parameter of setStat(). The cflow indicates that the localSet- Loc() pointcut should match when we are currently executing (cflow) within the setStats() method and a setLocation() join point is encountered. If a setLo- cation() join point is matched but it is outside the execution path starting and ending with the setStat() pointcut, it should be ignored.

When this pointcut is executed against the DVD class, the output obtained is

call(public void DVD.setLocation(java.lang.String, int)) Location and count = store 1

Title = Better Title

This output should be compared against the original pointcut without the cflow designator:

call(public void DVD.setLocation(java.lang.String, int)) call(public void DVD.setLocation(java.lang.String, int)) Location and count = New Store

Title = Better Title

Without the cflow() designator, a second setLocation() join point is matched within the main() function as well as in the setStats() method.

Multiple cflow Designators

If there are several places within your code where the join point in which you’re interested can be found, you can use multiple cflow designators. For example:

AspectJ Pointcuts

pointcut setLoc() :

call(public void DVD.setLocation(..)); pointcut setStat() :

call(public void DVD.setStats(String, int)); pointcut setAll() :

call(public void Boxset.setAll(..)); pointcut localSetLoc() :

setLoc() &&

(cflow(setStat()) || cflow(setAll()));

This code contains four pointcuts; we included the first three for use within the fourth. The code says that we are interested in a join point defined against the setLocation() method when a call to the method is made within either the DVD object’s setStats() method or the Boxset object’s setAll() method. If setLoca- tion() occurs in any other place, it is ignored.

Combining cflow Parameters

The previous example was based on the desire to match a single join point in two or more methods. The pointcut is built using a join point to be matched ANDed with two or more cflow designators. Let’s now turn our attention to a situation in which we want to add depth to the matching process. Consider the following class:

public class Flow { public void two() {

System.out.println("two"); }

public void one() { two();

}

public static void main(String args[]) { Flow flow = new Flow();

flow.one(); }

}

In this class, the main() method calls the one() method, which in turn calls the two() method. The two() method displays the value “two” on the console. If we want to match the println() method, the following pointcut will do the job:

pointcut callToPrint() :

call(void java.io.PrintStream.println(String));

Now we can easily match this join point within the call to the two() method by adding another pointcut and the cflow designator:

pointcut callToPrint() :

call(void java.io.PrintStream.println(String));

pointcut getTwo() : call(public void two()); pointcut matchPrint() :

callToPrint() && cflow(getTwo()) && !within(FlowAspect);

Now suppose we want to match the println() method call only when the two() method is called by the one() method. How about if we just add another cflow with a pointcut for the one() method as we did in the previous section? Well, this won’t do what we want because it tells the system to match the println() method when called from either the one() or two() method.

The solution is to combine the pointcuts for the one() and two() join points in the same cflow. For example:

pointcut getPrint() :

call(void java.io.PrintStream.println(String)); pointcut getTwo() :

call(public void two()); pointcut getOne() :

call(public void one()); pointcut matchPrint() :

getPrint() && cflow(getTwo() && getOne());

Here we find the matchPrint() pointcut where a match is made against the println() method and a combination of the one() and two() pointcuts. By com- bining both getOne() and getTwo() in the same cflow designator, we tell the system to match only when the println() method occurs in the execution flow of both the one() and two() methods. The only way the execution can occur in both methods is when the one() method calls the two() method or the two() method calls the one() method.

Combining cflow and Other Designators

It is possible to combine the cflow designator with all of the other ones we have discussed so far. For example, suppose we’re interested in the setLocation() method when it is called within the setStats() method but we also want to get a copy of the target object and the parameter being passed to setLocation(). The concern we are implementing might require that an inventory-tracking function occur when a DVD is moved from one store to another. Here’s what the new pointcut would look like:

pointcut setLoc() :

call(public void DVD.setLocation(..)); pointcut setStat() :

call(public void DVD.setStats(String, int));

AspectJ Pointcuts

pointcut localSetLoc(DVD dvd, String place) : setLoc() &&

cflow(setStat()) && target(dvd) && args(place, ..);

before(DVD dvd, String place) : localSetLoc(dvd, place) { System.out.println(thisJoinPoint.toLongString()); System.out.println("Incoming Location = " + place);

System.out.println("Current Location = " + dvd.getLocation()); }

The first thing you notice is the size of the third pointcut, which handles all of the join points we are interested in. Each of the designators in the localSetLoc() pointcut has a job to do, and only when all of the situations line up does the pointcut get matched. Here are the jobs of each designator:

setLoc() pointcut—Handles the matching of the setLocation() join point.

cflow(setStat()) designator—Handles matching only when the setLoc()

pointcut occurs during the execution of setStats().

target(dvd) designator—Returns the target DVD object of the setLoca-

tion() method.

args(place, ..)—Returns the first parameter to setLocation() and ignores all other parameters.

cflowbelow

In our example, the primary code is in the process of setting a location for a DVD object. If the code had been written so that another setLocation() method call could be made while the current setLocation() method is executing, the pointcut would match again potentially before the first code finished executing. This type of situation can occur when there is a hierarchy of objects, each with a polymorphic version of a single method. In many cases, you don’t want to han- dle all calls to a specific method—just the topmost call.

Consider this pointcut:

pointcut setLoc() :

call(public void DVD.setLocation(..)) || call(public void DVD.setStats(..)) || call(public void Boxset.setLocation(..)) || call(public void Boxset.add(..));

before() : setLoc() {

System.out.println(thisJoinPoint.toLongString()); }

In this pointcut we find a number of join points across both the DVD and Boxset classes, and all of the join points are ORed together, indicating what the set- Loc() pointcut should match when any of the join points are reached. From a concern standpoint, we are interested in knowing when the setLocation() method of any of the DVDs or Boxsets is called. When we execute the code against the Boxset class, we get the following output:

call(public void Boxset.add(DVD))

call(public void DVD.setStats(java.lang.String, int)) call(public void DVD.setLocation(java.lang.String, int))

If you look at the Boxset code, you notice that the add() method sets the loca- tion of the Boxset using a local setLocation() method and the setStats() method of the DVD class using its local setLocation() method. If either the add() or the setStats() method is used, we get a second match of the pointcut when the set- Location() method is called. We really don’t want this—we just want to know when the first of any join point listed in the pointcut is reached. The cflowbelow designator can help us with this. Consider this new pointcut using cflowbelow:

pointcut setLoc() :

call(public void DVD.setLocation(..)) || call(public void DVD.setStats(..)) || call(public void Boxset.setLocation(..)) || call(public void Boxset.add(..));

pointcut topSetLoc() : setLoc() && !cflowbelow(setLoc()); before() : topSetLoc() { System.out.println(thisJoinPoint.toLongString()); }

When this new pointcut is executed against the Boxset class, we obtain the fol- lowing output:

call(public void Boxset.add(DVD))

Only a single pointcut has been matched. Why? If you follow the code, the add() method calls the setStats() method of the DVD object, which in turn calls the setLocation() method. We only want to know about the add() method in this case. We do this by using the cflowbelow designator, which takes as a parame- ter a pointcut. Our pointcut topSetLoc() tells the system to first match any of the four join points listed in the setLoc() pointcut. When a match is found, the system should check to see if we are in the execution flow below the current join point. Below means not including the current join point. So, when the add() join point is reached, the setLoc() pointcut will match first. The topSetLoc() pointcut will match next, and the system will check to see if the add() method is in the execution flow.

AspectJ Pointcuts

When the setStats() method within the add() method is reached, the setLoc() pointcut will match. The topSetLoc() matches next, and the system checks the execution path of setStats(). Since the pointcut had previously matched, the system remembered that we are still within the execution path of add(), so cflowbelow doesn’t match. The same sequence of events occurs in the case of the setLocation() join point.

Let’s use an example based on a recursive algorithm. Here’s the code for a recursive factorial algorithm:

public class Fact {

public long factorial(int n) { if (n == 0) {

return 1; }

return n * factorial(n - 1); }

public static void main(String args[]) { Fact fact = new Fact();

System.out.println("Fact of 7 = " + fact.factorial(7)); }

}

Next we have a pointcut designed to return just the original call to the recursive algorithm:

pointcut getFact(int i) : call(public long factorial(i)); pointcut topGetFact2(int i) : getFact(i) && !cflowbelow(getFact(int)); before(int i) : topGetFact2(i) { System.out.println(thisJoinPoint.getSignature()); System.out.println(i); }

In this code, we have two different pointcuts. The first pointcut, called get- Fact(), is based on a join point for the factorial method in the primary code. There are no constraints for this pointcut; therefore, it will be matched each time the primary code calls the method. The topGetFact2() pointcut is a com- bination built by using the getFact() pointcut and the cflowbelow designator. This cflowbelow designator is built using the getFact() pointcut and will be matched if our current join point is called below the first call that the join point defined in the getFact() pointcut. Since our primary code is recursive, all calls except the first call to the factorial() method will be made below the first one. So, cflowbelow(getFact(int)) will be matched for all getFact() pointcuts except the very first one. The reason for this is the use of getFact(int) in the parameter to cflowbelow, which tells the system to use the join point when first encountered.

The output from the Fact class and our pointcut is as follows:

long Fact.factorial(int) 7

Fact of 7 = 5040

Next, we might be interested in each of the calls to the factorial join point as well as the original call used to start the recursion. The pointcut would now look like this:

pointcut topGetFact2(int i, int j) : getFact(i) &&

cflowbelow(cflow(getFact(j)) && !cflowbelow(getFact(int)));

before(int i, int j) : topGetFact2(i, j) {

System.out.println(thisJoinPoint.getSignature());

System.out.println("Current = " + i + " original = " + j); }

The topGetFact2() pointcut has two purposes. The first is to obtain the currently running factorial join point. This step is accomplished using the getFact() pointcut, which matches all factorial method calls and includes a parameter for the integer passed to the method.

The second purpose is to obtain the very first method call to the factorial join point. This step is accomplished using a rather ugly-looking cflowbelow desig- nator. Let’s begin talking about this part of the join point by looking at the pur- pose of the outer cflowbelow. The output for the entire pointcut is as follows:

current factorial call parameter value – original call parameter value

So if we call factorial(7) , the output looks like this:

Current = 6 original = 7

With this type of output, there is no purpose for the very first call to factorial. In other words, we don’t want to see output like

Current = 7 original = 7

Therefore, we define the pointcut as

getFact(i) && cflowbelow( )

This says that we want to know about all calls to the factorial join point but only after the first call to the join point. This combination allows us to bypass the Current = 7 original = 7 output.

Next, we want to be able to pull back the original factorial() method call param- eter value. This is accomplished with the code

cflow(getFact(j)) && !cflowbelow(getFact(int))

AspectJ Pointcuts

This pointcut combination says that we are interested in all calls to the factor- ial join point, including the first one—which is good because we need the orig- inal parameter value. In order to limit which join point call is matched, we created a combination with the inverse of a cflowbelow based on the factorial join point.

The output from the Fact class and this new pointcut is

long Fact.factorial(int) Current = 6 original = 7 long Fact.factorial(int) Current = 5 original = 7 long Fact.factorial(int) Current = 4 original = 7 long Fact.factorial(int) Current = 3 original = 7 long Fact.factorial(int) Current = 2 original = 7 long Fact.factorial(int) Current = 1 original = 7 long Fact.factorial(int) Current = 0 original = 7 Fact of 7 = 5040

Finally, suppose we’re interested in the current call to the factorial join point as well as the previous. The following pointcut will give us that information:

pointcut topGetFact(int i, int j) : getFact(i) &&

cflowbelow(getFact(j));

before(int i, int j) : topGetFact(i,j) {

System.out.println("Current = " + i + " Previous = " + j); }

The first part of the topGetFact() pointcut pulls all of the calls to the factorial join point and obtains the parameter value passed to the associated method. The second part of the pointcut obtains the previous join point by using the full cflowbelow designator. The output from the Fact class and this pointcut is as follows: Current = 6 Previous = 7 Current = 5 Previous = 6 Current = 4 Previous = 5 Current = 3 Previous = 4 Current = 2 Previous = 3 Current = 1 Previous = 2 Current = 0 Previous = 1 Fact of 7 = 5040 Combining Pointcuts 117