package org.simpleflatmapper.map.asm; import org.simpleflatmapper.ow2asm.ClassWriter; import org.simpleflatmapper.ow2asm.FieldVisitor; import org.simpleflatmapper.ow2asm.MethodVisitor; import org.simpleflatmapper.map.FieldMapper; import org.simpleflatmapper.map.MappingContext; import org.simpleflatmapper.map.mapper.AbstractMapper; import org.simpleflatmapper.reflect.BiInstantiator; import org.simpleflatmapper.reflect.Instantiator; import org.simpleflatmapper.reflect.asm.AsmUtils; import org.simpleflatmapper.util.TypeHelper; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.lang.reflect.Method; import static org.simpleflatmapper.ow2asm.Opcodes.AALOAD; import static org.simpleflatmapper.ow2asm.Opcodes.ACC_BRIDGE; import static org.simpleflatmapper.ow2asm.Opcodes.ACC_FINAL; import static org.simpleflatmapper.ow2asm.Opcodes.ACC_PRIVATE; import static org.simpleflatmapper.ow2asm.Opcodes.ACC_PROTECTED; import static org.simpleflatmapper.ow2asm.Opcodes.ACC_PUBLIC; import static org.simpleflatmapper.ow2asm.Opcodes.ACC_SUPER; import static org.simpleflatmapper.ow2asm.Opcodes.ACC_SYNTHETIC; import static org.simpleflatmapper.ow2asm.Opcodes.ALOAD; import static org.simpleflatmapper.ow2asm.Opcodes.ARETURN; import static org.simpleflatmapper.ow2asm.Opcodes.ASTORE; import static org.simpleflatmapper.ow2asm.Opcodes.CHECKCAST; import static org.simpleflatmapper.ow2asm.Opcodes.DUP; import static org.simpleflatmapper.ow2asm.Opcodes.GETFIELD; import static org.simpleflatmapper.ow2asm.Opcodes.INVOKESPECIAL; import static org.simpleflatmapper.ow2asm.Opcodes.INVOKEVIRTUAL; import static org.simpleflatmapper.ow2asm.Opcodes.NEW; import static org.simpleflatmapper.ow2asm.Opcodes.POP; import static org.simpleflatmapper.ow2asm.Opcodes.PUTFIELD; import static org.simpleflatmapper.ow2asm.Opcodes.RETURN; import static org.simpleflatmapper.ow2asm.Opcodes.V1_6; import static org.simpleflatmapper.reflect.asm.AsmUtils.toTargetTypeDeclaration; public class MapperAsmBuilder { private static final String ABSTRACT_MAPPER_TYPE = AsmUtils.toAsmType(AbstractMapper.class); private static final String FIELD_MAPPER_TYPE = AsmUtils.toAsmType(FieldMapper.class); private static final String INSTANTIATOR_TYPE = AsmUtils.toAsmType(BiInstantiator.class); private static final String mappingContextType = AsmUtils.toAsmType(MappingContext.class); public static <S,T> byte[] dump ( final String className, final FieldMapper<S, T>[] mappers, final FieldMapper<S, T>[] constructorMappers, final Class<? super S> sourceClass, final Class<T> target ) throws Exception { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); MethodVisitor mv; final String targetType = AsmUtils.toAsmType(target); final String classType = AsmUtils.toAsmType(className); cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, classType, "L" + ABSTRACT_MAPPER_TYPE + "<" + toTargetTypeDeclaration(targetType) + ">;", ABSTRACT_MAPPER_TYPE, null); for(int i = 0; i < mappers.length; i++) { declareMapperFields(cw, mappers[i], i); } for(int i = 0; i < constructorMappers.length; i++) { declareConstructorMapperFields(cw, constructorMappers[i], i); } { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "([L" + FIELD_MAPPER_TYPE + ";" + "[L" + FIELD_MAPPER_TYPE + ";L" + INSTANTIATOR_TYPE + ";)V", "(" + "[L" + FIELD_MAPPER_TYPE + "<" + toTargetTypeDeclaration(sourceClass) + toTargetTypeDeclaration(targetType) + ">;" + "[L" + FIELD_MAPPER_TYPE + "<" + toTargetTypeDeclaration(sourceClass) + toTargetTypeDeclaration(targetType) + ">;" + "L" + INSTANTIATOR_TYPE + "<" + toTargetTypeDeclaration(targetType) + ">;)V", null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 3); mv.visitMethodInsn(INVOKESPECIAL, ABSTRACT_MAPPER_TYPE, "<init>", "(L" + INSTANTIATOR_TYPE + ";)V", false); for(int i = 0; i < mappers.length; i++) { addFieldMapperInit(mv, mappers[i], i, classType); } for(int i = 0; i < constructorMappers.length; i++) { addGConstructorFieldMapperInit(mv, constructorMappers[i], i, classType); } mv.visitInsn(RETURN); mv.visitMaxs(3, 3); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "mapFields", "(" + toTargetTypeDeclaration(sourceClass) + toTargetTypeDeclaration(targetType) + toTargetTypeDeclaration(mappingContextType) +")V", null, new String[] { "java/lang/Exception" }); mv.visitCode(); for(int i = 0; i < mappers.length; i++) { generateMappingCall(mv, mappers[i], i, classType, AsmUtils.toAsmType(sourceClass), targetType); } mv.visitInsn(RETURN); mv.visitMaxs(3, 3); } { mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "mapFields", "(Ljava/lang/Object;Ljava/lang/Object;" + toTargetTypeDeclaration(mappingContextType) + ")V", null, new String[] { "java/lang/Exception" }); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, AsmUtils.toAsmType(sourceClass)); mv.visitVarInsn(ALOAD, 2); mv.visitTypeInsn(CHECKCAST, targetType); mv.visitVarInsn(ALOAD, 3); mv.visitMethodInsn(INVOKEVIRTUAL, classType, "mapFields", "(" + toTargetTypeDeclaration(sourceClass) + toTargetTypeDeclaration(targetType) + toTargetTypeDeclaration(mappingContextType)+ ")V", false); mv.visitInsn(RETURN); mv.visitMaxs(3, 3); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PROTECTED + ACC_FINAL, "mapToFields", "(" + toTargetTypeDeclaration(sourceClass) + toTargetTypeDeclaration(targetType) + toTargetTypeDeclaration(mappingContextType)+ ")V", null, new String[]{"java/lang/Exception"}); mv.visitCode(); for(int i = 0; i < constructorMappers.length; i++) { generateConstructorMappingCall(mv, constructorMappers[i], i, classType, AsmUtils.toAsmType(sourceClass), targetType); } mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ALOAD, 2); mv.visitVarInsn(ALOAD, 3); mv.visitMethodInsn(INVOKEVIRTUAL, classType, "mapFields", "(" + toTargetTypeDeclaration(sourceClass) + toTargetTypeDeclaration(targetType) + toTargetTypeDeclaration(mappingContextType)+ ")V", false); mv.visitInsn(RETURN); mv.visitMaxs(3, 3); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PROTECTED + ACC_BRIDGE + ACC_SYNTHETIC, "mapToFields", "(Ljava/lang/Object;Ljava/lang/Object;" + toTargetTypeDeclaration(mappingContextType) + ")V", null, new String[]{"java/lang/Exception"}); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, AsmUtils.toAsmType(sourceClass)); mv.visitVarInsn(ALOAD, 2); mv.visitTypeInsn(CHECKCAST, targetType); mv.visitVarInsn(ALOAD, 3); mv.visitMethodInsn(INVOKEVIRTUAL, classType, "mapToFields", "(" +toTargetTypeDeclaration(sourceClass) + toTargetTypeDeclaration(targetType)+ toTargetTypeDeclaration(mappingContextType) + ")V", false); mv.visitInsn(RETURN); mv.visitMaxs(3, 3); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null); mv.visitCode(); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false); mv.visitVarInsn(ASTORE, 1); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitLdcInsn("{"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitInsn(POP); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, ABSTRACT_MAPPER_TYPE, "appendToStringBuilder", "(Ljava/lang/StringBuilder;)V", false); mv.visitVarInsn(ALOAD, 1); for(int i = 0; i < mappers.length; i++) { String mapperName = ", fieldMapper" + i + "="; String mapper = String.valueOf(mappers[i]); mv.visitLdcInsn(mapperName); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitLdcInsn(mapper); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); } for(int i = 0; i < constructorMappers.length; i++) { String mapperName = ", constructorMapper" + i + "="; String mapper = String.valueOf(constructorMappers[i]); mv.visitLdcInsn(mapperName); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitLdcInsn(mapper); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); } mv.visitLdcInsn("}"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitInsn(ARETURN); mv.visitMaxs(2, 1); mv.visitEnd(); } cw.visitEnd(); return AsmUtils.writeClassToFile(className, cw.toByteArray()); } private static <S, T> void generateMappingCall(MethodVisitor mv, FieldMapper<S, T> mapper, int index, String classType, String sourceType, String targetType) { generateMappingCall(mv, mapper, index, classType, sourceType, targetType, "fieldMapper"); } private static <S, T> void generateConstructorMappingCall(MethodVisitor mv, FieldMapper<S, T> mapper, int index, String classType, String sourceType, String targetType) { generateMappingCall(mv, mapper, index, classType, sourceType, targetType, "constructorMapper"); } private static <S, T> void generateMappingCall(MethodVisitor mv, FieldMapper<S, T> mapper, int index, String classType, String sourceType, String targetType, String variablePrefix) { if (mapper ==null) return; Type mapperClass = AsmUtils.findClosestPublicTypeExposing(mapper.getClass(), FieldMapper.class); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, classType, variablePrefix + index, "L" + AsmUtils.toAsmType(mapperClass) + ";"); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ALOAD, 2); mv.visitVarInsn(ALOAD, 3); Method m = getMapToMethod(TypeHelper.toClass(mapperClass)); AsmUtils.invoke(mv, mapperClass, m); } private static Method getMapToMethod(Class<?> aClass) { Method m = null; for(Method p : aClass.getDeclaredMethods()) { if (!Modifier.isStatic(p.getModifiers()) && p.getName().equals("mapTo") && (p.getParameterTypes() != null && p.getParameterTypes().length == 3)) { // crude way of selecting non bridge method if (m == null || p.getModifiers() < m.getModifiers()) { m = p; } } } return m; } private static <S, T> void addFieldMapperInit(MethodVisitor mv, FieldMapper<S, T> mapper, int index, String classType) { if (mapper == null) return; Type mapperClass = AsmUtils.findClosestPublicTypeExposing(mapper.getClass(), FieldMapper.class); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); AsmUtils.addIndex(mv, index); mv.visitInsn(AALOAD); mv.visitTypeInsn(CHECKCAST, AsmUtils.toAsmType(mapperClass)); mv.visitFieldInsn(PUTFIELD, classType, "fieldMapper" + index, toTargetTypeDeclaration(AsmUtils.toAsmType(mapperClass))); } private static <S, T> void addGConstructorFieldMapperInit(MethodVisitor mv, FieldMapper<S, T> mapper, int index, String classType) { if (mapper == null) return; Type mapperClass = AsmUtils.findClosestPublicTypeExposing(mapper.getClass(), FieldMapper.class); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 2); AsmUtils.addIndex(mv, index); mv.visitInsn(AALOAD); mv.visitTypeInsn(CHECKCAST, AsmUtils.toAsmType(mapperClass)); mv.visitFieldInsn(PUTFIELD, classType, "constructorMapper" + index, toTargetTypeDeclaration(AsmUtils.toAsmType(mapperClass))); } private static <S, T> void declareMapperFields(ClassWriter cw, FieldMapper<S, T> mapper, int index) { if (mapper == null) return; FieldVisitor fv; Type mapperClass = AsmUtils.findClosestPublicTypeExposing(mapper.getClass(), FieldMapper.class); fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "fieldMapper" + index, toTargetTypeDeclaration(AsmUtils.toAsmType(mapperClass)), toTargetTypeDeclaration(AsmUtils.toGenericAsmType(mapperClass)), null); fv.visitEnd(); } private static <S, T> void declareConstructorMapperFields(ClassWriter cw, FieldMapper<S, T> mapper, int index) { if (mapper == null) return; FieldVisitor fv; Type mapperClass = AsmUtils.findClosestPublicTypeExposing(mapper.getClass(), FieldMapper.class); fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "constructorMapper" + index, toTargetTypeDeclaration(AsmUtils.toAsmType(mapperClass)), toTargetTypeDeclaration(AsmUtils.toGenericAsmType(mapperClass)), null); fv.visitEnd(); } }