Brian's Waste of Time

Sun, 22 Feb 2004

CFlow in DynAOP

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 =)

1 writebacks [/src/java/aop] permanent link