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