3 Replies Latest reply on Nov 22, 2009 4:07 AM by Stuart Douglas

    Creating AnnotationLiterals at runtime

    Stuart Douglas Master

      I have created a helper class the create AnnotationLiteral subclasses at runtime. E.g. if I have the following annotation:


      @Retention(RetentionPolicy.RUNTIME)
      public @interface SomeQualifier
      {
           public int someIntMethod();
      
           public SomeEnum someEnumMethod();
      }
      
      



      I can create concrete instances of it using:


        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("someIntMethod", 1);
        map.put("someEnumMethod", SomeEnum.A);
        SomeQualifier qual = AnnotationCache.getAnnotation(SomeQualifier.class, map);
      
      



      I thought I would post it here just in case it is useful to anyone writing portable extensions:


      /**
       * Class that generates an AnnotationLiteral for a given class type and loads it
       * with the appropriate class loader.
       * 
       * @author Stuart Douglas
       * 
       */
      public class AnnotationLiteralCreator
      {
      
           static int count = 0;
           private static final String ANNOTATION_CLASS_NAME = "javax.enterprise.util.AnnotationLiteral";
           private static final String CLASS_SUFFIX = "_$$_AnnotationProxy";
      
           /**
            * Created a class using javassist and loads it with the same class loader
            * that was used to load the annotation.
            */
           static public <T extends Annotation> Class<AnnotationLiteral<T>> createAnnotationLiteral(
                     Class<T> annotation)
           {
                try
                {
                     ClassFile cf = make(annotation);
                     ClassLoader cl = getClassLoader(annotation);
                     Class thisClass = FactoryHelper.toClass(cf, cl,
                               getDomain(annotation));
                     return (Class<AnnotationLiteral<T>>) thisClass;
                } catch (CannotCompileException e)
                {
                     throw new RuntimeException(e.getMessage(), e);
                }
           }
      
           static protected ClassLoader getClassLoader(Class annotation)
           {
                ClassLoader loader = annotation.getClassLoader();
                if (loader == null)
                {
                     loader = AnnotationLiteralCreator.class.getClassLoader();
                }
                return loader;
           }
      
           static protected ProtectionDomain getDomain(Class annotation)
           {
                return annotation.getProtectionDomain();
           }
      
           /**
            * generates the class file using javassist
            */
           static protected <T extends Annotation> ClassFile make(Class<T> annotation)
           {
      
                String className = annotation.getName() + CLASS_SUFFIX + count++;
                String anName = "L" + (annotation.getName().replace('.', '/')) + ";";
      
                ClassFile cf = new ClassFile(false, className, ANNOTATION_CLASS_NAME);
      
                try
                {
                     cf.setAccessFlags(AccessFlag.PUBLIC);
                     cf.addInterface(annotation.getName());
      
                     ConstPool cp = cf.getConstPool();
      
                     FieldInfo field = new FieldInfo(cp, "values", "Ljava/util/Map;");
                     field.setAccessFlags(AccessFlag.PRIVATE);
                     cf.addField(field);
      
                     // add the constructor that takes a map of values to return
                     // from methods that are named the same as the map keys
                     MethodInfo minfo = new MethodInfo(cp, "<init>",
                               "(Ljava/util/Map;)V");
                     minfo.setAccessFlags(Modifier.PUBLIC);
                     Bytecode code = new Bytecode(cp, 2, 2);
                     code.addAload(0);
                     code.addInvokespecial(ANNOTATION_CLASS_NAME, "<init>", "()V");
                     code.addAload(0);
                     code.addAload(1);
                     code.addPutfield(className, "values", "Ljava/util/Map;");
                     code.addOpcode(Opcode.RETURN);
                     minfo.setCodeAttribute(code.toCodeAttribute());
      
                     // add generic type information into the Signature Attribute
                     SignatureAttribute sig = new SignatureAttribute(cp,
                               "Ljavax/enterprise/util/AnnotationLiteral<" + anName + ">;"
                                         + anName + ";");
                     cf.addAttribute(sig);
                     cf.addMethod(minfo);
      
                     // go through and add implementations of any methods declared on the
                     // annotation
                     for (Method m : annotation.getDeclaredMethods())
                     {
                          if (m.getReturnType() == int.class)
                          {
                               addPrimitiveReturn(m, cf, cp, className,
                                         "java.lang.Integer", "intValue", "()I",
                                         Opcode.IRETURN);
                          } else if (m.getReturnType() == long.class)
                          {
                               addPrimitiveReturn(m, cf, cp, className, "java.lang.Long",
                                         "longValue", "()J", Opcode.LRETURN);
                          } else if (m.getReturnType() == byte.class)
                          {
                               addPrimitiveReturn(m, cf, cp, className, "java.lang.Byte",
                                         "byteValue", "()B", Opcode.IRETURN);
                          } else if (m.getReturnType() == char.class)
                          {
                               addPrimitiveReturn(m, cf, cp, className,
                                         "java.lang.Character", "charValue", "()C",
                                         Opcode.IRETURN);
                          } else if (m.getReturnType() == short.class)
                          {
                               addPrimitiveReturn(m, cf, cp, className, "java.lang.Short",
                                         "shortValue", "()S", Opcode.IRETURN);
                          } else if (m.getReturnType() == double.class)
                          {
                               addPrimitiveReturn(m, cf, cp, className,
                                         "java.lang.Double", "doubleValue", "()D",
                                         Opcode.DRETURN);
                          } else if (m.getReturnType() == float.class)
                          {
                               addPrimitiveReturn(m, cf, cp, className, "java.lang.Float",
                                         "floatValue", "()F", Opcode.FRETURN);
                          } else
                          {
                               addReferenceReturn(m, cf, cp, className, m.getReturnType()
                                         .getName());
                          }
                     }
                } catch (DuplicateMemberException e)
                {
                     // not going to happen
                }
                return cf;
      
           }
      
           /**
            * adds common code for returning primitive values from a map
            * 
            */
           private static void addPrimitiveReturn(Method m, ClassFile cf,
                     ConstPool cp, String className, String boxType, String unboxMethod,
                     String unboxSig, int retOpcode) throws DuplicateMemberException
           {
                MethodInfo mi = new MethodInfo(cp, m.getName(), methodDescriptor(m));
                mi.setAccessFlags(Modifier.PUBLIC);
      
                Bytecode code = new Bytecode(cp, 2, 2);
                // get the values map
                code.addAload(0);
                code.addGetfield(className, "values", "Ljava/util/Map;");
                // invoke map.get("methodname")
                code.addLdc(m.getName());
                code.addInvokeinterface("java.util.Map", "get",
                          "(Ljava/lang/Object;)Ljava/lang/Object;", 2);
                // cast to correct type
                code.addCheckcast(boxType);
                // now perform the unboxing
                code.addInvokevirtual(boxType, unboxMethod, unboxSig);
                // now return our int
                code.addOpcode(retOpcode);
                mi.setCodeAttribute(code.toCodeAttribute());
                cf.addMethod(mi);
           }
      
           private static void addReferenceReturn(Method m, ClassFile cf,
                     ConstPool cp, String className, String refType)
                     throws DuplicateMemberException
           {
                MethodInfo mi = new MethodInfo(cp, m.getName(), methodDescriptor(m));
                mi.setAccessFlags(Modifier.PUBLIC);
      
                Bytecode code = new Bytecode(cp, 2, 2);
                // get the values map
                code.addAload(0);
                code.addGetfield(className, "values", "Ljava/util/Map;");
                // invoke map.get("mathodname")
                code.addLdc(m.getName());
                code.addInvokeinterface("Ljava/util/Map;", "get",
                          "(Ljava/lang/Object;)Ljava/lang/Object;", 2);
                // cast to correct type
                code.addCheckcast(refType);
                // now return our reference
                code.addOpcode(Opcode.ARETURN);
                mi.setCodeAttribute(code.toCodeAttribute());
                cf.addMethod(mi);
           }
      
           // because we know they don't take any arguments we can cheat
           private static String methodDescriptor(Method m)
           {
                return "()" + classToStringRepresentation(m.getReturnType());
           }
      
           /**
            * Returns the internal name of a Type
            * 
            * @param c
            * @return
            */
           public static String classToStringRepresentation(Class c)
           {
                if (byte.class.equals(c))
                {
                     return "B";
                } else if (char.class.equals(c))
                {
                     return "C";
                } else if (double.class.equals(c))
                {
                     return "D";
                } else if (float.class.equals(c))
                {
                     return "F";
                } else if (int.class.equals(c))
                {
                     return "I";
                } else if (long.class.equals(c))
                {
                     return "J";
                } else if (short.class.equals(c))
                {
                     return "S";
                } else if (boolean.class.equals(c))
                {
                     return "Z";
                } else if (c.isArray())
                {
                     return c.getName().replace(".", "/");
                } else
                // normal object
                {
                     return extToInt(c.getName());
                }
           }
      
           public static String extToInt(String className)
           {
                String repl = className.replace(".", "/");
                return 'L' + repl + ';';
           }
      }
      
      




      
      public class AnnotationCache
      {
           static Map<Class, Class> cache = Collections
                     .synchronizedMap(new HashMap<Class, Class>());
      
           static public <T extends Annotation> T getAnnotation(Class<T> annotation,
                     Map<String, Object> values)
           {
                try
                {
                     if (!cache.containsKey(annotation))
                     {
                          Class cl = AnnotationLiteralCreator.createAnnotationLiteral(annotation);
                          cache.put(annotation, cl);
                     }
                     Class inst = cache.get(annotation);
                     Constructor c = inst.getConstructor(Map.class);
                     return (T) c.newInstance(values);
      
                } catch (Exception e)
                {
                     throw new RuntimeException(e);
                }
           }
      
      }