Bug 92889 - Explicit formals binding to constants wanted in pointcuts
Summary: Explicit formals binding to constants wanted in pointcuts
Status: REOPENED
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: 1.2.1   Edit
Hardware: PC Windows XP
: P5 enhancement with 1 vote (vote)
Target Milestone: ---   Edit
Assignee: Adrian Colyer CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-04-27 05:26 EDT by Dusan Chromy CLA
Modified: 2012-08-25 08:03 EDT (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Dusan Chromy CLA 2005-04-27 05:26:50 EDT
Consider the following code snippet from an aspect, assuming there is a class
Main with methods tigger() and foo() and that the foo() method can either be
called directly or from within tigger():

	pointcut T() :
	    execution(* Main.tigger());
	
	pointcut F() :
	    execution(* Main.foo()) 
	    && !cflow(T());
	
	Object around() : T() || F() {
	    // do something
	    Object z = proceed();
	    // do something else
            return z;
	}

As you can see, there is an around advice which does something when either
tigger() or foo() gets executed. So far so good, now assume the advice body
would like to know if it is being executed on behalf of pointcut T or F?
I could not think of any other way than creating two advices, one for T and one
for F. Unfortunately the two advices cannot share the same body, so the
programmer is forced into copy-paste-and-hack style. The two advices could
of course call a method (which could have a parameter indicating where it is
called from), however this does not go a long way because in a method you cannot 
call "proceed" or have access to thisJoinPoint reflections.

To solve this I suggest allowing explicit formals binding in pointcuts, like this:

	pointcut T(boolean flag) :
	    execution(* Main.tigger()) && bind(true);
	
	pointcut F(boolean flag) :
	    execution(* Main.foo()) 
	    && !cflow(T()) && bind(false);
	
	Object around(boolean flag) : T(flag) || F(flag) {
	    if (flag)
                // do something
            else
                // do something different
	    Object z = proceed();
	    // do something else
            return z;
	}

The whole process of binding resembles functional programming to me and allowing
explicit binding seems to me as a logical step.
Comment 1 Adrian Colyer CLA 2005-05-13 08:09:20 EDT
why not just use thisJoinPoint in the body of the advice? Since T and F match
different join points, this would always enable you to distinguish. If T and F
*could* both match at some join point, your proposal would break - what should
the value of the flag be? Advice executes at join points, not at pointcuts, so
the current thisJoinPoint abstraction is appropriate imho.
Comment 2 Julie Waterhouse CLA 2005-05-16 12:16:33 EDT
not sure why this was assigned to me - sending it back to Adrian...
Comment 3 Dusan Chromy CLA 2005-05-19 18:31:58 EDT
Hello Adrian,

thanks for your response. I even reassigned the bug to Julie in the false belief
that the bug report went unnoticed, sorry for that extra bother. A friend of
mine met you on the JAX in Frankfurt, so I see you must be busy these days.

I understand it is possible to examine thisJoinPoint in the advice body to
distinguish the cases in my example. However, it means the advice must know
about join point signatures, which is IMHO something that should be captured in
pointcuts only. I agree the binding would be impossible if T and F both matched
the same join point, but that's not really anything new or bad - there already
is an "Ambigous binding" error in AspectJ.

I admit my example was perhaps too abstract and not very convincing. Let me give
you a real code example that I wrote just today. We are using AspectJ to do
performance monitoring using Apache's StopWatch class. In some classes, I
introduce the StopWatch object to the class using inter-type declaration (more
precisely, I declare the class to extend a base class which has the StopWatch as
a member). In other classes, I just create a new StopWatch object for every
method invocation. This leads to a code with two very similar advices:

	Object around() : measuredMethod() {
		StopWatch w = new StopWatch();
		try {
			w.start();
			return proceed();
		} finally { w.stop(); // output w here }			    	}	

	Object around(StopWatchOwner owner) : measuredMethodWithOwnWatch(owner){
		try {
			owner.getWatch().start();
			return proceed(owner);
		} finally { owner.getWatch().stop(); // output owner.getWatch() here }
	}

As you see, the two advices are almost identical. A clear violation of the DRY
principle, isn't it? Yet the two cannot be merged, for the sole reason that one
pointcut has no arguments and the other one has one. What I would love to do is
this:

//redefine the pointcut that had originally no arguments
	pointcut measuredMethod(StopWatchOwner owner) :
		// whatever joinpoint definition here			    
		&& bind(null);

//and merge the two advices as:			    
	Object around(StopWatchOwner owner) : measuredMethod(owner) ||
measuredMethodWithOwnWatch(owner) {
		StopWatch w = (owner == null) ? new StopWatch() : owner.getWatch();
		try {
			w.start();
			return proceed();
		} finally { w.stop(); // output w here }			    	}	

What do you think?
Comment 4 Adrian Colyer CLA 2005-05-23 17:27:20 EDT
This example makes a lot more sense, thanks. I don't think we'll have time to
think through all of the potential issues in here in order to make the 1.5.0
release - and it's also something that no other user has asked for afaik. I'm
going to leave this open as an enhancement request but with no target milestone
assigned for the time being, that way we can come back and take another look
once we've dealt with the 1.5.0 release.
Comment 5 Adrian Colyer CLA 2005-08-26 11:24:18 EDT
We're not going to get to this in AJ 1.5.0. Marking as "LATER" for consideration
in 1.5.1 and future release planning.
Comment 6 Eclipse Webmaster CLA 2009-08-30 02:49:56 EDT
LATER/REMIND bugs are being automatically reopened as P5 because the LATER and REMIND resolutions are deprecated.
Comment 7 Alexander Kriegisch CLA 2012-08-25 08:03:19 EDT
I second this (old) request. My situation is as follows:

I have a logging aspect with several pointcuts and advice:

aspect LoggingAspect {
  pointcut logMe1() : execution(...);
  pointcut logMe2() : execution(...);
  pointcut logMe3() : execution(...);

  Object around() : logMe1() {
    String logMessage = "log message #1";
    logger.log("before " + logMessage);
    logger.indent();
    Object result = proceed();
    logger.dedent();
    logger.log("after " + logMessage);
  }

  Object around() : logMe2() {
    String logMessage = "log message #2";
    logger.log("before " + logMessage);
    logger.indent();
    Object result = proceed();
    logger.dedent();
    logger.log("after " + logMessage);
  }

  Object around() : logMe3() {
    String logMessage = "log message #1";
    logger.log("before " + logMessage);
    logger.indent();
    Object result = proceed();
    logger.dedent();
    logger.log("after " + logMessage);
  }
}

Obviously there is code duplication just because the log message is different. (Actually it is more complicated because sometimes the message is not constant, but needs to be constructed from a "this" object's properties, but let's forget about that for now.)

What I would like to have is this:

aspect LoggingAspect {
  pointcut logMe1(String msg) : execution(...) && bind(msg="log message #1");
  pointcut logMe2(String msg) : execution(...) && bind(msg="log message #2");
  pointcut logMe3(String msg) : execution(...) && bind(msg="log message #3");

  Object around(String msg) :
    logMe1(msg) || logMe2(msg) || logMe2(msg)
  {
    logger.log("before " + msg);
    logger.indent();
    Object result = proceed(msg);  // maybe even just proceed()
                                   // for bound pointcut parameters
    logger.dedent();
    logger.log("after " + msg);
  }
}