Earlier this week I was discussing AOP tools with Guillaume in irc and I said that you couldn't do cflow with dynaop or nanning -- that you needed AspectJ or AspektWerks to do that. This didn't really seem true -- and I was right that i was wrong. It took all of fifteen minutes to do!
Cflow is basically a pointcut based on the state of the stack -- is this point in execution upstack from that point, etc. For the proof of concept to myself I didn't provide cflow as a pointcut (as it probably should be) but rather as a combination of an interceptor and mixin. It works as demonstrated by a simple unit test::
import dynaop.*;
import junit.framework.TestCase;
import java.util.Map;
import java.util.HashMap;
public class CFlowTest extends TestCase {
public void testCFlow() throws Exception {
Aspects aspects = new Aspects();
aspects.interceptor(Pointcuts.instancesOf(Map.class),
Pointcuts.membersOf(Map.class),
new CFlowInterceptor());
aspects.mixin(Pointcuts.ALL_CLASSES, CFlowMixin.class, null);
final boolean[] called = { false };
aspects.interceptor(Pointcuts.instancesOf(Map.class),
Pointcuts.membersOf(Map.class),
new Interceptor() {
public Object intercept(final Invocation invocation)
throws Throwable {
called[0] = true;
assertTrue(invocation.getProxy() instanceof CFlowAware);
assertNotNull(((CFlowAware)invocation.getProxy()).getCFlow());
return invocation.proceed();
}
});
ProxyFactory fact = ProxyFactory.getInstance(aspects);
Map proxy = (Map) fact.wrap(new HashMap());
proxy.put("a", "b");
assertTrue(called[0]);
}
}
The silly part is how easy it was to do. This implementation won't be very fast, but it does work:
public interface CFlowAware
{
void setCFlow(StackTraceElement[] stack);
StackTraceElement[] getCFlow();
}
------------------------------------------------------
public class CFlowMixin implements CFlowAware {
private StackTraceElement[] stack;
public void setCFlow(StackTraceElement[] stack) {
this.stack = stack;
}
public StackTraceElement[] getCFlow() {
return this.stack;
}
}
------------------------------------------------------
import dynaop.Interceptor;
import dynaop.Invocation;
import dynaop.Proxy;
public class CFlowInterceptor implements Interceptor {
public Object intercept(Invocation invocation)
throws Throwable {
Exception e = new Exception();
StackTraceElement[] stack = e.getStackTrace();
e = null;
Proxy p = invocation.getProxy();
((CFlowAware)p).setCFlow(stack);
return invocation.proceed();
}
}
The CFlowInterceptor uses a hack to generate the stack trace -- it generates an exception and never throws it. There has to be a better way to do this, but I don't know it. It then passes the stack trace from the exception to the CFlowMixin which makes it availble downstream on the interception stack via the proxy =)