Mixins are definitely a good start, but nevertheless it's very hard, because I have to split the aspect into the static (mixin) and the dynamic part (interception). My feelings about this are that this is not in the spirit of AOP.
But let's get to the facts. As far as I understand the WIKI aspect example, I have to it this way:
Write an interface
Write an implementation of the interface (mixin)
Write two interceptor classes (aspects)
Here we go; First of all the interface:
public interface IStackHeight
{
public int size ();
}
Next comes the mixin. Here I am doing a cheap trick. I need the implementation of the size method in order to satisfy the compiler, but later on, I will use an around advice so this gets never executed; Dull dummy code.
public class StackHeight implements IStackHeight
{
public int size ()
{
return 0;
}
}
The StackHeightAspect class. The implementation is similar to the wiki:
import org.jboss.aop.joinpoint.*;
public class StackHeightAspect
{
private int height;
public Object pushInvocation ( MethodInvocation invocation ) throws Throwable
{
++ this.height;
return invocation.invokeNext();
}
public Object popInvocation ( MethodInvocation invocation ) throws Throwable
{
if ( this.height > 0 )
{
-- this.height;
}
return invocation.invokeNext();
}
public int sizeInvocation ( MethodInvocation invocation ) throws Throwable
{
return this.height;
}
}
Everyone should see the cheap trick I am doing here, the actual implementation of the size method is here, not in the mixin! Apart from that I am not sure if the chosen return type, int, is correct. But how would have been the implementation if I am to return an Object?
Now the last part, the synchronization aspect, should be straight forward, but I have my problems here, too:
import org.jboss.aop.joinpoint.*;
public class StackSynchronizationAspect
{
private boolean semaphore = false;
public Object synchronize ( MethodInvocation invocation ) throws Throwable
{
// synchronized ( invocation.getInstance () )
synchronized ( this )
{
if ( this.semaphore == true )
{
try {
this.wait ();
} catch(InterruptedException e) {}
this.semaphore = true;
}
}
Object result = invocation.invokeNext();
// synchronized ( invocation.getInstance () )
synchronized ( this )
{
this.semaphore = false;
this.notify ();
}
return result;
}
}
I want to express in the synchronized statements that the synchronization is about the instance of the message receiver. That's what invocation.getInstance should indicate. How is this expressed? Surely somehow with context information, but the docs are very sparse about that.
Now let's put the pieces together:
<?xml version="1.0" encoding="UTF-8"?>
<aop>
<aspect class="StackHeightAspect" scope="PER_INSTANCE"/>
<aspect class="StackSynchronizationAspect" scope="PER_INSTANCE"/>
<introduction class="Stack">
<mixin>
<interfaces>
IStackHeight
</interfaces>
<class>StackHeight</class>
</mixin>
</introduction>
<pointcut name="pushExecution" expr="execution(void Stack->push(Object))" />
<pointcut name="popExecution" expr="execution(Object Stack->pop())" />
<pointcut name="sizeExecution" expr="execution(int Stack->size())" />
<bind pointcut="popExecution">
<advice name="popInvocation" aspect="StackHeightAspect"/>
</bind>
<bind pointcut="pushExecution OR popExecution OR sizeExecution">
<advice name="synchronize" aspect="StackHeightAspect"/>
</bind>
<bind pointcut="pushExecution">
<advice name="pushInvocation" aspect="StackHeightAspect"/>
</bind>
<bind pointcut="sizeExecution">
<advice name="sizeInvocation" aspect="StackHeightAspect"/>
</bind>
</aop>
This is my test driver (synchronization is not tested here):
public class Driver
{
public static void main ( String args [] ) throws Exception
{
Stack s = new Stack ();
Driver.printHeight ( s );
s.push ( new Integer ( 5 ) );
Driver.printHeight ( s );
s.push ( new Integer ( 5 ) );
s.pop ();
Driver.printHeight ( s );
}
private static void printHeight ( Stack s ) throws Exception
{
try
{
System.out.println ( "Stack size: " + ( (IStackHeight) s ).size () );
}
catch ( Exception e )
{
System.err.println ( "Introduction failed: " + e );
}
}
}
These are the results of my test run:
prilmeie@atlab1 /cygdrive/f/Inetpub/cvsroot/da-aop/source/stack/jboss-aop
$ rm *.class; ant
Buildfile: build.xml
prepare:
compile:
[javac] Compiling 6 source files to F:\Inetpub\cvsroot\da-aop\source\stack\jboss-aop
[aopc] [deploying] file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <aspect> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <aspect> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <introduction> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <pointcut> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <pointcut> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <pointcut> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <bind> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <bind> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <bind> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [deploy] <bind> file:/F:/Inetpub/cvsroot/da-aop/source/stack/jboss-aop/jboss-aop.xml
[aopc] [trying to transform] Driver
[aopc] [no comp needed] F:\Inetpub\cvsroot\da-aop\source\stack\jboss-aop\Driver.class
[aopc] [cannot compile] isInterface: IStackHeight
[aopc] [no comp needed] F:\Inetpub\cvsroot\da-aop\source\stack\jboss-aop\IStackHeight.class
[aopc] [trying to transform] Stack$StackObject
[aopc] [no comp needed] F:\Inetpub\cvsroot\da-aop\source\stack\jboss-aop\Stack$StackObject.class
[aopc] [trying to transform] Stack
[aopc] [compiled] F:\Inetpub\cvsroot\da-aop\source\stack\jboss-aop\Stack.class
[aopc] [trying to transform] StackHeight
[aopc] [no comp needed] F:\Inetpub\cvsroot\da-aop\source\stack\jboss-aop\StackHeight.class
[aopc] [trying to transform] StackHeightAspect
[aopc] [no comp needed] F:\Inetpub\cvsroot\da-aop\source\stack\jboss-aop\StackHeightAspect.class
[aopc] [trying to transform] StackSynchronizationAspect
[aopc] [no comp needed] F:\Inetpub\cvsroot\da-aop\source\stack\jboss-aop\StackSynchronizationAspect.class
run:
[java] Introduction failed: java.lang.ClassCastException
[java] Introduction failed: java.lang.ClassCastException
[java] Introduction failed: java.lang.ClassCastException
BUILD SUCCESSFUL
Total time: 3 seconds
So the whole work was for nothing? - There must be a simple explanation for this, what went wrong?
Trying to disassemble Stack:
prilmeie@atlab1 /cygdrive/f/Inetpub/cvsroot/da-aop/source/stack/jboss-aop
$ javap -c Stack
Compiled from "Stack.java"
public class Stack extends java.lang.Object implements org.jboss.aop.Advised,IStackHeight{
protected transient org.jboss.aop.ClassInstanceAdvisor _instanceAdvisor;
public Stack();
Code: [...]
Why does the class cast fail? The whole package can be loaded at
http://home.in.tum.de/prilmeie/jboss-aop-stack.tar.gz], please make sure that jboss-common.jar, jboss-aop.jar, javassist.jar, concurrent.jar, trove.jar and qdox.jar are in the same directory as the extracted files are. Otherwise it will not work
Apart from that I am very dissatisfied with the results. I had to use a cheap trick to get the results I wanted. Worse, the StackHeightAspect is now a necessary part of the Stack class, otherwise I never can use the size method. I even have to use the silly class cast whenever I want to use that specific method. A maintenance nightmare! - There must be a better way to achieve these results (This was my first attempt to use JBoss AOP, used version is beta3)
Are there any other mistakes I have made?