Harlan Slater - 2007-03-16

Firstly, I would just like to say what a great piece of software RetroWeaver is. Considering the job it is doing the fact that it worked out of the box enabling me to use a tool written for 1.5 was an absolute life saver.

I am currently investigating whether it would be possible to use RetroWeaver within a ClassLoader to automatically weave classes as they are being loaded into the system. The advantage of doing this at runtime, rather than compile time is that if the system is 1.5+ then the class does not need modification and does not incur the overhead (albeit slight in most cases) of being Woven. However, I have encountered a couple of problems with Annotations and I just wanted to understand the rational behind the current Annotation support. I have not raised a bug report for this as I do not consider this to be a bug, I am using it in a way for which it was not designed, however if I can get it working then I am quite prepared to contribute any enhancements / fixes I find if they are more generally useful.

I had one minor problem in AnnotationImpl.createAnnotation() where it was using the ClassLoader for the AnnotationImpl.class to create the proxy but Proxy.newProxyInstance() failed because it could not find the annotationType class using the supplied class loader. I changed this to use the ClassLoader for the annotationType and that fixed the problem.

The main problem is to do with runtime access to the annotations. I have the following annotation class and test class.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Details {
    String name();
    int version();
}

@Details(name = "1234", version = 1)
public class AnnotationTest
        extends AbstractTest {

    protected void runImpl() {
        Class clazz = getClass();
        Details details = (Details) clazz.getAnnotation(Details.class);
        System.out.println("Name: " + details.name());
        System.out.println("Version: " + details.version());
    }
}

As you can see the annotation class is itself annotated with a couple of standard Java Annotations to control how the annotation can be used. If I don't have the first one then I get a NullPointerException in the test which is understandable as Java does not have to make the annotations available at runtime. However, if I do add it then I get the following stack trace.

Caused by: net.sourceforge.retroweaver.runtime.java.lang.annotation.AnnotationFormatError: [Retroweaver] Unable to find class: java.lang.annotation.Retention
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB$AbstractAnnotationVisitor.getClass(AIB.java:405)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB$TopLevelAnnotation.<init>(AIB.java:543)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB.visitAnnotation(AIB.java:228)
        at org.objectweb.asm.ClassReader.accept(Unknown Source)
        at org.objectweb.asm.ClassReader.accept(Unknown Source)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB.readClassStream(AIB.java:69)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB.<init>(AIB.java:61)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB.getAib(AIB.java:218)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB$TopLevelAnnotation.<init>(AIB.java:543)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB.visitAnnotation(AIB.java:228)
        at org.objectweb.asm.ClassReader.accept(Unknown Source)
        at org.objectweb.asm.ClassReader.accept(Unknown Source)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB.readClassStream(AIB.java:69)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB.<init>(AIB.java:61)
        at net.sourceforge.retroweaver.runtime.java.lang.annotation.AIB.getAib(AIB.java:218)
        at net.sourceforge.retroweaver.runtime.java.lang.Class_.getAnnotation(Class_.java:36)

The problem is not due to a missing runtime library as many of these problems are, I have debugged into the code and RetroWeaver has correctly woven the Details and AnnotationTest classes during which it found the 'net.sourceforge.retroweaver.runtime.java.lang.annotation.Retention' class. The problem is arising because AIB is reading the annotations directly from the class in the JAR. This assumes that the class in the JAR has been woven and so does not try and translate the names again, hence the reason it ends up looking for 'java.lang.annotation.Retention' instead of 'net.sourceforge.retroweaver.runtime.java.lang.annotation.Retention' and therefore fails.

I have read some of the other topics relating to implementations of Annotation, specifically:
* https://sourceforge.net/forum/forum.php?thread_id=1376335&forum_id=358878
* https://sourceforge.net/forum/forum.php?thread_id=1207583&forum_id=358878

and from them I was under the impression that the currently implemented approach was not as robust as Toby would have liked. Of course those topics are quite old so it may be that things have changed since then but I could not find anything more about them, or any other documentation. I have also looked at the code, and apart from the class comment on AIB (which appears to be slightly out of date as it mentions a ANNOTATIONS_FIELD to which I can find no other reference) I can't find any information about why this approach was taken.

Apart from the fact that it does not work when weaving on load I can see a number of issues with this approach.
* It could become a memory leak, as each class with annotations has an entry in the AIB classDescriptors. In a dynamic class loading environment like OSGi where classes can come and go this will keep classes in memory even though they are no longer required.
* I know that ASM is very fast but reprocessing the class file cannot be particular efficient.

Accepting that there may be other very good reasons why Annotations are supported this way it seems to me that a better approach would be for the weaver to generate code to populate a static AIB field with the same information that is currently retrieved dynamically. AIB could then extract that information from the class, possibly using reflection. It would address both the above issues, as no references to the AIB would exist outside the class to which it belongs, and it will not require runtime processing of the class file. The main problem that I can see with this approach is just the complexity of the code necessary to populate the AIB.

If there are no other issues that I am not aware of then I would be quite happy to work on the above approach.