|
April 2004
[
tirsen
]
17:33, Sunday, 25 April 2004
On an internal ThoughtWorks conference I presented a couple of cool examples of what you can do with AspectJ (avoiding the rightfully impopular and boing logging example, which was appreciated by the audience). Most of them was blatantly stolen from Ramnivas Laddad's superb book AspectJ in Action (hope you don't mind Ramnivas ;-) ). Read it if you haven't yet, I cannot recommend it enough. I rewrote them using TDD on the train back and forth to my current project outside of London. One of the more popular ones (and one of the few I did not steal from Ramnivas) was the undo framework. Anyone that has written a large GUI application knows that undo is a major pain in the ass, each command has to written twice, how to do it and how to undo it. Well, with AspectJ things like this becomes quite easy and can be written completely modularized in a generic "non-intrusive" fashion. Here's a test-case that demonstrates the usage of the framework:
UndoChain undoChain = new UndoChain();
final Point point = new Point(100, 100);
undoChain.execute(new Runnable() {
public void run() {
point.x = 150;
}
});
assertEquals(150, point.x);
undoChain.undo();
assertEquals(100, point.x);
undoChain.redo();
assertEquals(150, point.x);
A Point is created and the x-coordinate is modified in a command. undoChain.undo() rolls back all modifications done by the command and undoChain.redo() reapplies them again. Ok, so here's the gist of the solution, an aspect that records all field modifications their previous value and their new value.
aspect RecordFieldModifications
{
/**
* Picks out the execution of a command.
*/
pointcut inCommand(Command cmd) :
this(cmd) &&
execution(* Command.execute());
/**
* Picks out any modification of a field.
*/
pointcut fieldModification(Object o, Object newValue) :
args(newValue) && this(o) &&
set(* *.*);
/**
* Picks out any modification of a field caused by the execution of
* a Command, the old value can unfortunately not be picked out by
* the pointcut. :-(
*/
pointcut fieldModificationFromCommand(Command cmd, Object o, Object newValue) :
!within(UndoChain) &&
cflow(inCommand(cmd)) &&
fieldModification(o, newValue);
/**
* Advice to any field modification caused by the execution of a field. Stores
* the new value and the old value as a Modification on the Command.
*/
before(Command cmd, Object o, Object newValue) :
fieldModificationFromCommand(cmd, o, newValue)
{
// retrieve extra context information via reflection
Field field = getField(thisJoinPoint.getSignature());
Object oldValue = field.get(o);
// record modification in currently executing command
cmd.addModification(o, field, oldValue, newValue);
}
/**
* Gets the field using reflection.
*/
private Field getField(Signature signature) {
Field field = signature.getDeclaringType().getDeclaredField(signature.getName());
field.setAccessible(true);
return field;
}
}
The rest of the solution is basically just plumbing, to undo a command all modifications are undoed in sequence. Undo of a single modification is basically just about restoring the old value of the field using reflection: field.set(o, oldValue); The whole solution clocks in at 156 lines of code, which includes imports, package statements, and quite lengthy comments. Neat, huh!?
[
tirsen
]
15:50, Thursday, 1 April 2004
This is a great new innovation for TCP/IP: James, maybe it was too early to repent Jelly. Imagine if you combined Jelly and XCP, your TCP packages would not only be in XML, you would also be able to execute them!! We are definitely making progress. |