I played with Simone's code a bit, and it does seem to work as described.
Here's the solution I came up with for when the constructor call is
made within a constructor call.
This solution works assuming that the only places where A or it's
subclasses are instantiated within the constructor of A or it's
subclasses are in code that can be woven, which, for my project, is a
safe assumption.
I made the class abstract so that it can be easily reused whenever
this pattern is appropriate.
I don't like having the getMyClass() method, but I don't know of a
good way to get the generic type without it.
I am also caching the return value from isAssignableFrom, since this
call appears to be relatively expensive in some versions of java.
It's still a bit of a hack, but it seems to fulfill my needs.
public abstract aspect InitializationAspect<T> {
/* Cache to avoid reflective calls */
private Map<String, Boolean> isAssignableMap = new HashMap<String, Boolean>();
/**
* Returns the class highest in the hierarchy. Should be T.
*/
protected abstract Class<T> getMyClass();
/**
* The Advice to perform after an object has been instantiated.
* @param t The object that was created.
*/
protected abstract void myAdvice(T t);
/**
* Checks to make sure that the constructor that is executing
* is the type of the object to be created (i.e. lowest on the stack)
* and calls myAdvice() if it is. Does nothing if not.
* @param t The object that was created
*/
after(T t) : execution(T+.new(..)) && this(t) {
if (isLastConstructorInChain(getMyClass())) {
myAdvice(t);
}
}
/**
* Captures any calls to create a new object of type T or it's subtypes
* that happen within type T or it's subtypes, and calls myAdvice().
*/
after() returning(T t): call(T+.new()) && withincode(T+.new(..)) {
myAdvice(t);
}
/**
* Returns true if a class' constructor the lowest method call in a
* chain of constructor calls. If it isn't, return true if the next lowest
* call is not type T or a subclass of type T, false otherwise.
* @param clazz The class to look for.
*/
public boolean isLastConstructorInChain(Class<?> clazz) {
StackTraceElement[] stackTrace =
Thread.currentThread().getStackTrace();
// find the first constructor, cause there are a few AspectJ
internal calls before it
int acpos = 0;
while (acpos < stackTrace.length &&
!stackTrace[acpos].getMethodName().equals("<init>")) {
acpos++;
}
// Check if we run out, should never happen
if (acpos >= stackTrace.length) {
return false;
}
// Now we have the last call to <init>
// if the following one is not a constructor (or is not present),
// we can skip all the checks and return true
if (acpos + 1 == stackTrace.length ||
!stackTrace[acpos + 1].getMethodName().equals("<init>")) {
return true;
}
// Otherwise we have to check it the next constructor in the
// chain is a proper subclass of the current class
String callingClass = stackTrace[acpos + 1].getClassName();
Boolean isAssignable = isAssignableMap.get(callingClass);
if(isAssignable == null) {
Class<?> forname = null;
try {
forname = Class.forName(callingClass);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
isAssignable = false;
}
isAssignable = !clazz.isAssignableFrom(forname);
isAssignableMap.put(callingClass, isAssignable);
}
return isAssignable;
}
}
public aspect AAspect extends InitializationAspect<A> {
protected Class<A> getMyClass() {
return A.class;
}
protected void myAdvice(A a) {
System.out.println("Done:" + a.getClass());
}
}