14 Replies Latest reply on Jul 4, 2009 9:57 PM by paulkeeble

    adding annotations

    cat4hire

      Hi,
      can anybody point me to a simple example of how to add an annotation to a class thru javassist?

      Thanks,
      Luca

        • 1. Re: adding annotations

          Hi Luca,
          Check the AnnotationsAttribute JDoc.

          ClassFile cf = ... ;
          ConstPool cp = cf.getConstPool();
          AnnotationsAttribute attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);

          Annotation a = new Annotation("Author", cp);
          a.addMemberValue("name", new

          StringMemberValue("Chiba", cp));
          attr.setAnnotation(a);
          cf.addAttribute(attr);
          cf.setVersionToJava5();

          Guy

          • 2. Re: adding annotations
            cat4hire

            Hi,
            thanks for your reply, I was having a look at annotation_attribute, annotationwriter and annotationattribute, and now I've got a doubt: is the annotation only stored in the class file? Because what I'd like to obtain is to "inject" an annotation into a class (Runtime retention) when the class is loaded, without modifying the class file. But if I use the ClassFile object I have to modify the class file on the disk, is this correct? Is there a way to add an annotation at load time?

            Thanks,
            Luca

            • 3. Re: adding annotations

              To add annotation to a class on load time the cleaner way to do it is to use javaagent.
              See:
              http://javahowto.blogspot.com/2006/07/javaagent-option.html

              Guy

              • 4. Re: adding annotations
                cat4hire

                I'm not sure about the use of an agent, because it will perform the annotation addition at the program load time, while I have to perform it depending on run-time conditions at class loading time, and most important I could have separate class loaders that add or not the annotations to the class they are loading. Is this possible with the agents?

                • 5. Re: adding annotations
                  cat4hire

                  Just to make it more clear: I've got a special class loader that is going to subclass a class that it must load to add specific behaviours, one of such behaviour should be an annotation. That is why I'm searching to do them thru Javassist.
                  Any suggestion?

                  • 6. Re: adding annotations
                    cat4hire

                    This is what I've tried to do in my class loader:

                     subProxyClassName += this.getSubClassNameSuffix();
                    
                     // make a new class and prepare the annotation
                     ClassFile classFile = new ClassFile( false, subProxyClassName, baseProxyClass.getName() );
                     ConstPool constantPool = classFile.getConstPool();
                     AnnotationsAttribute attribute = new AnnotationsAttribute( constantPool, AnnotationsAttribute.visibleTag );
                     javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation( this.annotationClassName, constantPool );
                     attribute.setAnnotation( annotation );
                     classFile.addAttribute( attribute );
                     classFile.setVersionToJava5();
                     classFile.setSuperclass( baseProxyClass.getName() );
                     ByteArrayOutputStream bos = new ByteArrayOutputStream();
                     DataOutputStream os = new DataOutputStream( bos );
                     classFile.write( os );
                     bytecode = bos.toByteArray();
                     return this.defineClass(subProxyClassName, bytecode, 0, bytecode.length);
                    


                    The above is the findClass method of my class loader. The idea is that I have to create a subclass of the baseProxyClass (a CtClass object) and I have to add to it the annotation indicated by annotationClassName. When I return from the findClass method, the Class object I've got says me that it is a subclass of my proxy, but it cannot be instantiated as a proxy object (InstantiationError), as well as the getAnnotations() method returns an empty array.
                    What am I doing wrong?

                    Thanks,
                    Luca

                    • 7. Re: adding annotations
                      cat4hire

                      I've tried to define the class within the class loader, introspecting it in place to see what happens. In other words, I added the following code:


                       subProxyClassName += this.getSubClassNameSuffix();
                      
                       // make a new class and prepare the annotation
                       ClassFile classFile = new ClassFile( false, subProxyClassName, baseProxyClass.getName() );
                       ConstPool constantPool = classFile.getConstPool();
                       AnnotationsAttribute attribute = new AnnotationsAttribute( constantPool, AnnotationsAttribute.visibleTag );
                       javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation( this.annotationClassName, constantPool );
                       attribute.setAnnotation( annotation );
                       classFile.addAttribute( attribute );
                       classFile.setVersionToJava5();
                       classFile.setSuperclass( baseProxyClass.getName() );
                       ByteArrayOutputStream bos = new ByteArrayOutputStream();
                       DataOutputStream os = new DataOutputStream( bos );
                       classFile.write( os );
                       bytecode = bos.toByteArray();
                       baseProxyClass = null;
                      
                       Class clazz = this.defineClass(subProxyClassName, bytecode, 0, bytecode.length);
                       System.out.println("Class " + clazz.getName());
                       System.out.println("Superclass " + clazz.getSuperclass());
                       System.out.println("Annotations");
                       try {
                       AgentProxy aProxy = (AgentProxy) clazz.newInstance();
                       } catch (InstantiationException e) {
                       // TODO Auto-generated catch block
                       e.printStackTrace();
                       } catch (IllegalAccessException e) {
                       // TODO Auto-generated catch block
                       e.printStackTrace();
                       }
                       for(Annotation a : clazz.getAnnotations())
                       System.out.println("annotazione " + a);
                      


                      I believe that the way I'm using the ClassFile is wrong, since I'm not able to load the class, or better the class that is loaded is unable to be instantied as child of the parent class.
                      The class hierarchy is the following:
                      proxy
                      ^
                      |
                      proxy_roled_1_24325842 <- when creating this class
                      I need to inject an annotation


                      Class whitecat.proxy_roled_1_24325842
                      Superclass class whitecat.proxy
                      Annotations
                      java.lang.InstantiationException: whitecat.proxy_roled_1_24325842
                       at java.lang.Class.newInstance0(Class.java:340)
                       at java.lang.Class.newInstance(Class.java:308)
                       at whitecat.core.RoleEngine.findClass(RoleEngine.java:701)
                       at whitecat.core.RoleEngine.injectAnnotation(RoleEngine.java:538)
                       at whitecat.example.ExampleMain.run(ExampleMain.java:132)
                       at java.lang.Thread.run(Thread.java:619)
                      



                      Any suggestion about this?

                      • 8. Re: adding annotations
                        cat4hire

                        I've made another try: I've tried to get the classfile from a CtClass object instantied with the class name of the base class:

                         // make a new class and prepare the annotation
                         CtClass newProxyClass = pool.makeClass(subProxyClassName);
                         ClassFile classFile = newProxyClass.getClassFile(); //new ClassFile( false, subProxyClassName, baseProxyClass.getName() );
                         ConstPool constantPool = classFile.getConstPool();
                         AnnotationsAttribute attribute = new AnnotationsAttribute( constantPool, AnnotationsAttribute.visibleTag );
                         javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation( this.annotationClassName, constantPool );
                         attribute.setAnnotation( annotation );
                         classFile.addAttribute( attribute );
                         classFile.setVersionToJava5();
                         classFile.setName(subProxyClassName);
                         classFile.setSuperclass( baseProxyClass.getName() );
                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
                         DataOutputStream os = new DataOutputStream( bos );
                         classFile.write( os );
                         bytecode = bos.toByteArray();
                        


                        but again, when I try to instantiate this class, that is of kind "proxy", I got an instantiation error:

                        Class clazz = this.defineClass(subProxyClassName, bytecode, 0, bytecode.length);
                        AgentProxy aProxy = (AgentProxy) clazz.newInstance();
                        


                        I got the same instantiation exception of the previous post.
                        Any idea?

                        • 9. Re: adding annotations
                          cat4hire

                          I've found how to make the ClassFile working for defining my new class, but I'm still unable to add the annotation. Now I work as follows:
                          1) I create a CtClass object and set the inheritance chain on that;
                          2) I get the byte[] from the CtClass and place them into a ByteArrayInputStream, used to create a ClassFile;
                          3) I add the annotation to the classfile;
                          4) I get the byte[] from the classfile thru a ByteArrayOutputStream;
                          5) I load the class thru the defineClass method.

                          Now the class is of the right type, but it has no the annotation.
                          The following is my code; anyone has an idea of which is my error adding the annotation?

                           subProxyClassName += this.getSubClassNameSuffix();
                          
                           // make a new class and prepare the annotation
                           CtClass newProxyClass = pool.makeClass(subProxyClassName);
                           newProxyClass.setSuperclass( baseProxyClass );
                           ByteArrayInputStream is = new ByteArrayInputStream( newProxyClass.toBytecode() );
                           DataInputStream dis = new DataInputStream( is );
                           ClassFile classFile = new ClassFile( dis );
                           newProxyClass.defrost();
                           ConstPool constantPool = classFile.getConstPool();
                           AnnotationsAttribute attribute = new AnnotationsAttribute( constantPool, AnnotationsAttribute.visibleTag );
                           javassist.bytecode.annotation.Annotation annotation = new javassist.bytecode.annotation.Annotation( this.annotationClassName, constantPool );
                           attribute.setAnnotation( annotation );
                           classFile.addAttribute( attribute );
                           classFile.setVersionToJava5();
                           ByteArrayOutputStream bos = new ByteArrayOutputStream();
                           DataOutputStream os = new DataOutputStream( bos );
                           classFile.write( os );
                          new javassist.bytecode.annotation.Annotation( constantPool, subProxy );
                           bytecode = bos.toByteArray();
                           baseProxyClass = null;
                          
                          ...
                          
                           Class clazz = this.defineClass(subProxyClassName, bytecode, 0, bytecode.length);
                          
                          


                          Thanks,
                          Luca

                          • 10. Re: adding annotations
                            cat4hire

                            I've made another experiment: using an AnnotationsWriter as follows does not work too:

                            
                             ByteArrayOutputStream aos = new ByteArrayOutputStream();
                             AnnotationsWriter aw = new AnnotationsWriter(aos, constantPool);
                             aw.numAnnotations(1);
                             aw.annotation(this.annotationClassName, 0);
                             aw.close();
                             byte ab[] = aos.toByteArray();
                             attribute = new AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag, ab);
                            
                             System.out.println("Annotation attribute " + attribute.getName() + " " + attribute.getAnnotations().length);
                             classFile.addAttribute( attribute );
                            


                            The class can be loaded and instantiated, but I cannot access its annotations.
                            In both cases, if I print the classfile content with a ClassFileWriter I got a message that lets me think I've got the annotations:

                            attribute: SourceFile (2 byte): javassist.bytecode.SourceFileAttribute
                            attribute: RuntimeVisibleAnnotations (6 byte): javassist.bytecode.AnnotationsAttribute
                            


                            Moreover, in the case in which I use an AnnotationsWriter I got the following run-time exception when I try to access the annotations thru the Class.getAnnotations() method:

                            Exception in thread "Thread-Example-Main" java.lang.reflect.GenericSignatureFormatError
                             at sun.reflect.generics.parser.SignatureParser.error(SignatureParser.java:103)
                             at sun.reflect.generics.parser.SignatureParser.parseFieldTypeSignature(SignatureParser.java:233)
                             at sun.reflect.generics.parser.SignatureParser.parseTypeSignature(SignatureParser.java:359)
                             at sun.reflect.generics.parser.SignatureParser.parseTypeSig(SignatureParser.java:157)
                             at sun.reflect.annotation.AnnotationParser.parseSig(AnnotationParser.java:367)
                             at sun.reflect.annotation.AnnotationParser.parseAnnotation(AnnotationParser.java:181)
                             at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:69)
                             at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:52)
                             at java.lang.Class.initAnnotationsIfNecessary(Class.java:3072)
                             at java.lang.Class.getAnnotations(Class.java:3052)
                            


                            I cannot find whre I'm doing something wrong.

                            • 11. Re: adding annotations
                              cat4hire

                              I'm going mad, anyone with a suggestion?

                              I've found that the following piece of code:

                               ByteArrayOutputStream aos = new ByteArrayOutputStream();
                               AnnotationsWriter aw = new AnnotationsWriter(aos, constantPool);
                               aw.numAnnotations(1);
                               aw.annotation(this.annotationClassName, 0);
                               aw.close();
                               byte ab[] = aos.toByteArray();
                               System.out.println("\n\t++++Annotation bytecode for annotation " + this.annotationClassName + "++++");
                               System.out.println(new String(ab) );
                               System.out.println("\n\t++++++++++++++\n");
                               attribute = new AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag, ab);
                              


                              that should create the annotation specified, prints the following:

                              
                               ++++Annotation bytecode for annotation whitecat.example.DBRoleAnnotation++++
                              
                              
                               +++++++++++++++++++++++
                              
                              


                              So I'm pretty sure that the bytecode generated for the annotation is empty, and that the annotation is regularly added to my class, but without a bytecode I cannot access it. Anyone can explain why the bytecode is null?

                              • 12. Re: adding annotations
                                cat4hire

                                Hi,
                                I'm still fighting with the annotation problem. I've made a code clean up, and now I've got the following piece of code:

                                 // make a new class
                                 CtClass newProxyClass = pool.makeClass( subProxyClassName );
                                 newProxyClass.setSuperclass( baseProxyClass );
                                
                                 // place a default constructor in the new class
                                 CtConstructor constructor = new CtConstructor(null, newProxyClass );
                                 constructor.setBody(";");
                                 newProxyClass.addConstructor(constructor);
                                
                                 // get the class file and add the annotation
                                 ClassFile classFile = newProxyClass.getClassFile();
                                 ConstPool constantPool = classFile.getConstPool();
                                 AnnotationsAttribute attr = new AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag);
                                 javassist.bytecode.annotation.Annotation a = new javassist.bytecode.annotation.Annotation(this.annotationClassName, constantPool);
                                 attr.setAnnotation(a);
                                 classFile.addAttribute(attr);
                                 classFile.setVersionToJava5();
                                
                                 // transform the classfile into bytecode
                                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
                                 DataOutputStream os = new DataOutputStream( bos );
                                 classFile.write( os );
                                 os.close();
                                
                                 // load the class
                                 return this.defineClass(subProxyClassName, bytecode, 0, bytecode.length);
                                


                                at runtime no exception are thrown, but the new class still has no annotation added!

                                Then I saved the bytecode to a file in two cases, the above piece of code and the one without the addAttribute, and I can see they are different, so it seems as the annotation is added, but it is not visible to the runtime system.

                                Please, any suggestion is appreciated!!!

                                Thanks,
                                Luca

                                • 13. Re: adding annotations
                                  cat4hire

                                  After a lot I found that the following is the code that works for me:

                                   ClassFile classFile = newProxyClass.getClassFile();
                                   ConstPool constantPool = classFile.getConstPool();
                                   AnnotationsAttribute attr = new AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag);
                                   javassist.bytecode.annotation.Annotation a = new javassist.bytecode.annotation.Annotation(this.annotationClassName, constantPool);
                                   attr.setAnnotation(a);
                                   classFile.addAttribute(attr);
                                   classFile.setVersionToJava5();
                                  
                                  
                                   ByteArrayOutputStream bos = new ByteArrayOutputStream();
                                   DataOutputStream os = new DataOutputStream( bos );
                                   classFile.write( os );
                                   os.close();
                                  
                                   // all done
                                   return bos.toByteArray();
                                  


                                  the above produces the byte code for a class with the added annotation.

                                  Thanks,
                                  Luca

                                  • 14. Re: adding annotations
                                    paulkeeble

                                    The beauty of a Java agent is it gets passed every loaded class, regardless of classloader. So although you need to filter the list as quickly as possible (you'll get thousands of calls from java.* classes for instance) it is the best way to do the instrumentation.

                                    I have happily got annotation adding working in my code base recently so thought it might help you if I explained roughly how I do it.

                                    First up getting a CtClass:

                                    byte[] byteCode = ....
                                    ClassPool pool = ....
                                    CtClass clazz = pool.makeClass(new ByteArrayInputStream(byteCode));


                                    If your using a Java agent it will give you the bytes directly, if you load them yourself in a custom classloader.

                                    Next up you the annotations adding to a CtClass:

                                    CtClass clazz = ....
                                    ClassFile classFile = clazz.getClassFile();
                                    ConstPool cp = classFile.getConstPool();

                                    AnnotationsAttribute attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);

                                    Annotation theAnnotation = new Annotation("org.package.AnnotationType",cp);
                                    annotation.addMemberValue("name", new StringMemberValue("Paul",cp));

                                    attr.setAnnotation(theAnnotation);

                                    clazz.getClassInfo().addAttribute(attr);


                                    That worked well for me as an approach. If you want to see a working version (the far more complicated version based on methods and arrays of values) you can see the latest version of my code at JEMM Git MethodAnnotationTransformation.java