Creating AnnotationLiterals at runtime
swd847 Nov 22, 2009 1:42 AMI 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); } } }