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);
}
}
}