blob: 0cf4264fc07fd17d5df6f3a0c557c5fdbd8e065a [file] [log] [blame]
package org.codehaus.jackson.map.deser;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class BeanHelper implements Opcodes {
public static class BeanBuilder {
protected Map<String, Class> properties = new LinkedHashMap<String, Class>();
protected Map<String, ThrowMethodType> throwMethods = new LinkedHashMap<String, ThrowMethodType>();
protected List<Class> implementing = new ArrayList<Class>();
protected String className;
protected String internalClass;
public BeanBuilder(String className) {
this.className = className;
this.internalClass = getInternalClassName(className);
}
public BeanBuilder implement(Class parent) {
this.implementing.add(parent);
for (Method m : parent.getMethods()) {
if (m.getName().startsWith("get")
|| m.getName().startsWith("set")) {
String name = getFieldName(m.getName());
Class propType = m.getName().startsWith("get") ? m
.getReturnType() : m.getParameterTypes()[0];
if (this.properties.containsKey(name)
&& !this.properties.get(name).equals(propType)) {
throw new IllegalArgumentException("Duplicate property");
}
addProperty(name, propType);
} else {
addThrow(m.getName(), m.getParameterTypes(), m
.getReturnType(),
UnsupportedOperationException.class);
}
}
return this;
}
public BeanBuilder addProperty(String name, Class type) {
properties.put(name, type);
return this;
}
public BeanBuilder addThrow(String name, Class[] paramTypes,
Class returnType, Class exceptionType) {
this.throwMethods.put(name, new ThrowMethodType(name, paramTypes,
returnType, exceptionType));
return this;
}
public Class load() {
ClassWriter cw = new ClassWriter(0);
String[] parents = new String[implementing.size()];
for (int i = 0; i < implementing.size(); i++) {
parents[i] = getInternalClassName(implementing.get(i).getName());
}
cw.visit(V1_2, ACC_PUBLIC + ACC_SUPER, internalClass, null,
"java/lang/Object", parents);
cw.visitSource(className + ".java", null);
BeanHelper.generateDefaultConstructor(cw);
for (Map.Entry<String, Class> propEntry : this.properties
.entrySet()) {
String propName = propEntry.getKey();
Class propClass = propEntry.getValue();
BeanHelper.createField(cw, propName, propClass);
BeanHelper.createGetter(cw, internalClass, propName, propClass);
BeanHelper.createSetter(cw, internalClass, propName, propClass);
}
for (Map.Entry<String, ThrowMethodType> throwEntry : this.throwMethods
.entrySet()) {
ThrowMethodType thr = throwEntry.getValue();
BeanHelper.createThrow(cw, this.internalClass, throwEntry
.getKey(), thr.paramTypes, thr.returnType,
thr.exceptionType);
}
cw.visitEnd();
return loadClass(className, cw.toByteArray());
}
}
private static void generateDefaultConstructor(ClassWriter cw) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null,
null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
private static void createField(ClassWriter cw, String fieldName,
Class fieldType) {
String javaType = getLValue(fieldType);
FieldVisitor fv = cw.visitField(0, fieldName, javaType, null, null);
fv.visitEnd();
}
private static void createSetter(ClassWriter cw, String internalClassName,
String fieldName, Class fieldType) {
String methodName = getSetterName(fieldName);
String returnType = getLValue(fieldType);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, "("
+ returnType + ")V", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, internalClassName, fieldName, returnType);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
private static void createGetter(ClassWriter cw, String internalClassName,
String fieldName, Class fieldType) {
String methodName = getGetterName(fieldName);
String returnType = getLValue(fieldType);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, "()"
+ returnType, null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, internalClassName, fieldName, returnType);
mv.visitInsn(ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
private static void createThrow(ClassWriter cw, String internalClassName,
String methodName, Class[] inTypes, Class returnType,
Class exceptionType) {
String rTypeName = getLValue(returnType);
String exceptionName = getInternalClassName(exceptionType.getName());
String sig = "(" + getArgumentsType(inTypes) + ")" + rTypeName;
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, sig, null,
null);
mv.visitTypeInsn(NEW, exceptionName);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, exceptionName, "<init>", "()V");
mv.visitInsn(ATHROW);
mv.visitMaxs(2, 1 + inTypes.length);
mv.visitEnd();
}
private static Class loadClass(String className, byte[] b) {
// override classDefine (as it is protected) and define the class.
Class clazz = null;
try {
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class cls = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method = cls.getDeclaredMethod(
"defineClass", new Class[] { String.class, byte[].class,
int.class, int.class });
// protected method invocaton
method.setAccessible(true);
try {
Object[] args = new Object[] { className, b, new Integer(0),
new Integer(b.length) };
clazz = (Class) method.invoke(loader, args);
} finally {
method.setAccessible(false);
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
return clazz;
}
private static String getInternalClassName(String className) {
return className.replace(".", "/");
}
private static String getFieldName(String getterMethodName) {
char[] name = getterMethodName.substring(3).toCharArray();
name[0] = Character.toLowerCase(name[0]);
final String propName = new String(name);
return propName;
}
private static String getLValue(Class fieldType) {
if (fieldType == null || fieldType.equals(void.class)) {
return "V";
}
String plainR = fieldType.getName();
String rType = getInternalClassName(plainR);
String javaType = "L" + rType + ";";
return javaType;
}
private static String getGetterName(String fieldName) {
return "get" + fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1);
}
private static String getSetterName(String fieldName) {
return "set" + fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1);
}
private static String getArgumentsType(Class[] inTypes) {
StringBuilder list = new StringBuilder();
for (Class clazz : inTypes) {
list.append(getLValue(clazz));
}
return list.toString();
}
private static class ThrowMethodType {
public final String name;
public final Class[] paramTypes;
public final Class returnType;
public final Class exceptionType;
public ThrowMethodType(String name, Class[] paramTypes,
Class returnType, Class exceptionType) {
this.name = name;
this.paramTypes = paramTypes;
this.returnType = returnType;
this.exceptionType = exceptionType;
}
}
}