package net.minidev.asm;

import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.BIPUSH;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.F_SAME;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.ICONST_1;
import static org.objectweb.asm.Opcodes.ICONST_2;
import static org.objectweb.asm.Opcodes.ICONST_3;
import static org.objectweb.asm.Opcodes.ICONST_4;
import static org.objectweb.asm.Opcodes.ICONST_5;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.IFNE;
import static org.objectweb.asm.Opcodes.IFNULL;
import static org.objectweb.asm.Opcodes.IF_ICMPNE;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.RETURN;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class BeansAccessBuilder {
	static private String METHOD_ACCESS_NAME = Type.getInternalName(BeansAccess.class);

	final Class<?> type;
	final Accessor[] accs;
	final DynamicClassLoader loader;
	final String className;
	final String accessClassName;
	final String accessClassNameInternal;
	final String classNameInternal;
	final HashMap<Class<?>, Method> convMtds = new HashMap<Class<?>, Method>();
//	Class<? extends Exception> exeptionClass = net.minidev.asm.ex.NoSuchFieldException.class;
	Class<? extends Exception> exeptionClass = NoSuchFieldException.class;

	
Build reflect bytecode from accessor list.
Params:
  • type – type to be access
  • accs – used accessor
  • loader – Loader used to store the generated class
/** * Build reflect bytecode from accessor list. * * @param type * type to be access * @param accs * used accessor * @param loader * Loader used to store the generated class */
public BeansAccessBuilder(Class<?> type, Accessor[] accs, DynamicClassLoader loader) { this.type = type; this.accs = accs; this.loader = loader; this.className = type.getName(); if (className.startsWith("java.")) this.accessClassName = "net.minidev.asm." + className + "AccAccess"; else this.accessClassName = className.concat("AccAccess"); this.accessClassNameInternal = accessClassName.replace('.', '/'); this.classNameInternal = className.replace('.', '/'); } public void addConversion(Iterable<Class<?>> conv) { if (conv == null) return; for (Class<?> c : conv) addConversion(c); } public void addConversion(Class<?> conv) { if (conv == null) return; for (Method mtd : conv.getMethods()) { if ((mtd.getModifiers() & Modifier.STATIC) == 0) continue; Class<?>[] param = mtd.getParameterTypes(); if (param.length != 1) continue; if (!param[0].equals(Object.class)) continue; Class<?> rType = mtd.getReturnType(); if (rType.equals(Void.TYPE)) continue; convMtds.put(rType, mtd); } } public Class<?> bulid() { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); MethodVisitor mv; boolean USE_HASH = accs.length > 10; int HASH_LIMIT = 14; String signature = "Lnet/minidev/asm/BeansAccess<L" + classNameInternal + ";>;"; cw.visit(Opcodes.V1_6, ACC_PUBLIC + Opcodes.ACC_SUPER, accessClassNameInternal, signature, METHOD_ACCESS_NAME, null); // init { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, METHOD_ACCESS_NAME, "<init>", "()V"); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); } // set(Object object, int methodIndex, Object value) mv = cw.visitMethod(ACC_PUBLIC, "set", "(Ljava/lang/Object;ILjava/lang/Object;)V", null, null); mv.visitCode(); // if no Field simply return if (accs.length == 0) { // // mv.visitInsn(RETURN); } else if (accs.length > HASH_LIMIT) { // lots of field Use Switch Statement mv.visitVarInsn(ILOAD, 2); Label[] labels = ASMUtil.newLabels(accs.length); Label defaultLabel = new Label(); mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); int i = 0; for (Accessor acc : accs) { mv.visitLabel(labels[i++]); if (!acc.isWritable()) { mv.visitInsn(RETURN); continue; } internalSetFiled(mv, acc); } mv.visitLabel(defaultLabel); } else { Label[] labels = ASMUtil.newLabels(accs.length); int i = 0; for (Accessor acc : accs) { ifNotEqJmp(mv, 2, i, labels[i]); internalSetFiled(mv, acc); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); i++; } } if (exeptionClass != null) throwExIntParam(mv, exeptionClass); else mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); // public Object get(Object object, int fieldId) mv = cw.visitMethod(ACC_PUBLIC, "get", "(Ljava/lang/Object;I)Ljava/lang/Object;", null, null); mv.visitCode(); // if (USE_HASH) if (accs.length == 0) { mv.visitFrame(F_SAME, 0, null, 0, null); } else if (accs.length > HASH_LIMIT) { mv.visitVarInsn(ILOAD, 2); Label[] labels = ASMUtil.newLabels(accs.length); Label defaultLabel = new Label(); mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); int i = 0; for (Accessor acc : accs) { mv.visitLabel(labels[i++]); mv.visitFrame(F_SAME, 0, null, 0, null); if (!acc.isReadable()) { mv.visitInsn(ACONST_NULL); mv.visitInsn(ARETURN); continue; } mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, classNameInternal); Type fieldType = Type.getType(acc.getType()); if (acc.isPublic()) { mv.visitFieldInsn(GETFIELD, classNameInternal, acc.getName(), fieldType.getDescriptor()); } else { String sig = Type.getMethodDescriptor(acc.getter); mv.visitMethodInsn(INVOKEVIRTUAL, classNameInternal, acc.getter.getName(), sig); } ASMUtil.autoBoxing(mv, fieldType); mv.visitInsn(ARETURN); } mv.visitLabel(defaultLabel); mv.visitFrame(F_SAME, 0, null, 0, null); } else { Label[] labels = ASMUtil.newLabels(accs.length); int i = 0; for (Accessor acc : accs) { ifNotEqJmp(mv, 2, i, labels[i]); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, classNameInternal); Type fieldType = Type.getType(acc.getType()); if (acc.isPublic()) { mv.visitFieldInsn(GETFIELD, classNameInternal, acc.getName(), fieldType.getDescriptor()); } else { if (acc.getter == null) throw new RuntimeException("no Getter for field " + acc.getName() + " in class " + this.className); String sig = Type.getMethodDescriptor(acc.getter); mv.visitMethodInsn(INVOKEVIRTUAL, classNameInternal, acc.getter.getName(), sig); } ASMUtil.autoBoxing(mv, fieldType); mv.visitInsn(ARETURN); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); i++; } } if (exeptionClass != null) throwExIntParam(mv, exeptionClass); else { mv.visitInsn(ACONST_NULL); mv.visitInsn(ARETURN); } mv.visitMaxs(0, 0); mv.visitEnd(); if (!USE_HASH) { // Object get(Object object, String methodName) mv = cw.visitMethod(ACC_PUBLIC, "set", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)V", null, null); mv.visitCode(); Label[] labels = ASMUtil.newLabels(accs.length); int i = 0; for (Accessor acc : accs) { mv.visitVarInsn(ALOAD, 2); mv.visitLdcInsn(acc.fieldName); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z"); mv.visitJumpInsn(IFEQ, labels[i]); internalSetFiled(mv, acc); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); i++; } if (exeptionClass != null) throwExStrParam(mv, exeptionClass); else mv.visitInsn(RETURN); mv.visitMaxs(0, 0); // 2,4 mv.visitEnd(); } if (!USE_HASH) { // get(Object object, String methodName) mv = cw.visitMethod(ACC_PUBLIC, "get", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", null, null); mv.visitCode(); Label[] labels = ASMUtil.newLabels(accs.length); int i = 0; for (Accessor acc : accs) { mv.visitVarInsn(ALOAD, 2); // methodName mv.visitLdcInsn(acc.fieldName); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z"); mv.visitJumpInsn(IFEQ, labels[i]); mv.visitVarInsn(ALOAD, 1); // object mv.visitTypeInsn(CHECKCAST, classNameInternal); Type fieldType = Type.getType(acc.getType()); if (acc.isPublic()) { mv.visitFieldInsn(GETFIELD, classNameInternal, acc.getName(), fieldType.getDescriptor()); } else { String sig = Type.getMethodDescriptor(acc.getter); mv.visitMethodInsn(INVOKEVIRTUAL, classNameInternal, acc.getter.getName(), sig); } ASMUtil.autoBoxing(mv, fieldType); mv.visitInsn(ARETURN); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); i++; } if (exeptionClass != null) throwExStrParam(mv, exeptionClass); else { mv.visitInsn(ACONST_NULL); mv.visitInsn(ARETURN); } mv.visitMaxs(0, 0); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "()Ljava/lang/Object;", null, null); mv.visitCode(); mv.visitTypeInsn(NEW, classNameInternal); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, classNameInternal, "<init>", "()V"); mv.visitInsn(ARETURN); mv.visitMaxs(2, 1); mv.visitEnd(); } cw.visitEnd(); byte[] data = cw.toByteArray(); // dumpDebug(data, "/tmp/debug-" + accessClassName + ".txt"); return loader.defineClass(accessClassName, data); }
Dump Generate Code
/** * Dump Generate Code */
@SuppressWarnings("unused") private void dumpDebug(byte[] data, String destFile) { // try { // File debug = new File(destFile); // int flags = ClassReader.SKIP_DEBUG; // ClassReader cr = new ClassReader(new ByteArrayInputStream(data)); // cr.accept(new ASMifierClassVisitor(new PrintWriter(debug)), // ASMifierClassVisitor.getDefaultAttributes(), // flags); // } catch (Exception e) { // } }
Dump Set Field Code
Params:
  • mv –
  • acc –
/** * Dump Set Field Code * * @param mv * @param acc */
private void internalSetFiled(MethodVisitor mv, Accessor acc) { /** * FNC params * * 1 -> object to alter * * 2 -> id of field * * 3 -> new value */ mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, classNameInternal); // get VELUE mv.visitVarInsn(ALOAD, 3); Type fieldType = Type.getType(acc.getType()); Class<?> type = acc.getType(); String destClsName = Type.getInternalName(type); Method conMtd = convMtds.get(type); if (conMtd != null) { // external converion String clsSig = Type.getInternalName(conMtd.getDeclaringClass()); String mtdName = conMtd.getName(); String mtdSig = Type.getMethodDescriptor(conMtd); mv.visitMethodInsn(INVOKESTATIC, clsSig, mtdName, mtdSig); } else if (acc.isEnum()) { // builtIn Enum Conversion Label isNull = new Label(); mv.visitJumpInsn(IFNULL, isNull); mv.visitVarInsn(ALOAD, 3); // mv.visitTypeInsn(CHECKCAST, "java/lang/String"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;"); mv.visitMethodInsn(INVOKESTATIC, destClsName, "valueOf", "(Ljava/lang/String;)L" + destClsName + ";"); mv.visitVarInsn(ASTORE, 3); mv.visitLabel(isNull); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, this.classNameInternal); // "net/minidev/asm/bean/BEnumPriv" mv.visitVarInsn(ALOAD, 3); mv.visitTypeInsn(CHECKCAST, destClsName); } else if (type.equals(String.class)) { // built In String Conversion Label isNull = new Label(); mv.visitJumpInsn(IFNULL, isNull); mv.visitVarInsn(ALOAD, 3); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;"); mv.visitVarInsn(ASTORE, 3); mv.visitLabel(isNull); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, this.classNameInternal); mv.visitVarInsn(ALOAD, 3); mv.visitTypeInsn(CHECKCAST, destClsName); } else { // just check Cast mv.visitTypeInsn(CHECKCAST, destClsName); } if (acc.isPublic()) { mv.visitFieldInsn(PUTFIELD, classNameInternal, acc.getName(), fieldType.getDescriptor()); } else { String sig = Type.getMethodDescriptor(acc.setter); mv.visitMethodInsn(INVOKEVIRTUAL, classNameInternal, acc.setter.getName(), sig); } mv.visitInsn(RETURN); }
add Throws statement with int param 2
/** * add Throws statement with int param 2 */
private void throwExIntParam(MethodVisitor mv, Class<?> exCls) { String exSig = Type.getInternalName(exCls); mv.visitTypeInsn(NEW, exSig); mv.visitInsn(DUP); mv.visitLdcInsn("mapping " + this.className + " failed to map field:"); mv.visitVarInsn(ILOAD, 2); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "toString", "(I)Ljava/lang/String;"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;"); mv.visitMethodInsn(INVOKESPECIAL, exSig, "<init>", "(Ljava/lang/String;)V"); mv.visitInsn(ATHROW); }
add Throws statement with String param 2
/** * add Throws statement with String param 2 */
private void throwExStrParam(MethodVisitor mv, Class<?> exCls) { String exSig = Type.getInternalName(exCls); mv.visitTypeInsn(NEW, exSig); mv.visitInsn(DUP); mv.visitLdcInsn("mapping " + this.className + " failed to map field:"); mv.visitVarInsn(ALOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;"); mv.visitMethodInsn(INVOKESPECIAL, exSig, "<init>", "(Ljava/lang/String;)V"); mv.visitInsn(ATHROW); }
dump a Jump if not EQ
/** * dump a Jump if not EQ */
private void ifNotEqJmp(MethodVisitor mv, int param, int value, Label label) { mv.visitVarInsn(ILOAD, param); if (value == 0) { /* notest forvalue 0 */ mv.visitJumpInsn(IFNE, label); } else if (value == 1) { mv.visitInsn(ICONST_1); mv.visitJumpInsn(IF_ICMPNE, label); } else if (value == 2) { mv.visitInsn(ICONST_2); mv.visitJumpInsn(IF_ICMPNE, label); } else if (value == 3) { mv.visitInsn(ICONST_3); mv.visitJumpInsn(IF_ICMPNE, label); } else if (value == 4) { mv.visitInsn(ICONST_4); mv.visitJumpInsn(IF_ICMPNE, label); } else if (value == 5) { mv.visitInsn(ICONST_5); mv.visitJumpInsn(IF_ICMPNE, label); } else if (value >= 6) { mv.visitIntInsn(BIPUSH, value); mv.visitJumpInsn(IF_ICMPNE, label); } else { throw new RuntimeException("non supported negative values"); } } }