package org.simpleflatmapper.reflect.asm;
import org.simpleflatmapper.ow2asm.ClassWriter;
import org.simpleflatmapper.ow2asm.FieldVisitor;
import org.simpleflatmapper.ow2asm.MethodVisitor;
import org.simpleflatmapper.reflect.BuilderInstantiatorDefinition;
import org.simpleflatmapper.reflect.instantiator.ExecutableInstantiatorDefinition;
import org.simpleflatmapper.reflect.Getter;
import org.simpleflatmapper.reflect.Instantiator;
import org.simpleflatmapper.reflect.Parameter;
import org.simpleflatmapper.util.TypeHelper;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Map.Entry;
import static org.simpleflatmapper.ow2asm.Opcodes.ACC_BRIDGE;
import static org.simpleflatmapper.ow2asm.Opcodes.ACC_FINAL;
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.ACONST_NULL;
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.INVOKEINTERFACE;
import static org.simpleflatmapper.ow2asm.Opcodes.INVOKESPECIAL;
import static org.simpleflatmapper.ow2asm.Opcodes.INVOKESTATIC;
import static org.simpleflatmapper.ow2asm.Opcodes.INVOKEVIRTUAL;
import static org.simpleflatmapper.ow2asm.Opcodes.NEW;
import static org.simpleflatmapper.ow2asm.Opcodes.PUTFIELD;
import static org.simpleflatmapper.ow2asm.Opcodes.RETURN;
import static org.simpleflatmapper.ow2asm.Opcodes.V1_6;
public class InstantiatorBuilder {
public static <S> byte[] createInstantiator(final String className, final Class<?> sourceClass,
final ExecutableInstantiatorDefinition instantiatorDefinition, final Map<Parameter, Getter<? super S, ?>> injections) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
Class<?> targetClass= TypeHelper.toClass(BiInstantiatorBuilder.getTargetType(instantiatorDefinition));
String targetType = AsmUtils.toAsmType(targetClass);
String sourceType = AsmUtils.toWrapperType(sourceClass);
String classType = AsmUtils.toAsmType(className);
String instantiatorType = AsmUtils.toAsmType(Instantiator.class);
cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, classType, "Ljava/lang/Object;L" + instantiatorType + "<L" + targetType + ";>;", "java/lang/Object",
new String[] { instantiatorType });
Parameter[] parameters = instantiatorDefinition.getParameters();
appendGetters(injections, cw);
appendInit(injections, cw, sourceType, classType);
appendNewInstanceBuilder(sourceClass, instantiatorDefinition, injections, cw, targetType, sourceType, classType, parameters);
appendBridgeMethod(cw, targetType, sourceType, classType);
appendToString(injections, cw, parameters);
cw.visitEnd();
return AsmUtils.writeClassToFile(className, cw.toByteArray());
}
public static <S> byte[] createInstantiator(final String className, final Class<?> sourceClass,
final Instantiator<Void, ?> builderInstantiator,
final BuilderInstantiatorDefinition instantiatorDefinition,
final Map<Parameter, Getter<? super S, ?>> injections) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
Class<?> targetClass= TypeHelper.toClass(BiInstantiatorBuilder.getTargetType(instantiatorDefinition));
String targetType = AsmUtils.toAsmType(targetClass);
String sourceType = AsmUtils.toWrapperType(sourceClass);
String classType = AsmUtils.toAsmType(className);
String instantiatorType = AsmUtils.toAsmType(Instantiator.class);
cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, classType, "Ljava/lang/Object;L" + instantiatorType + "<L" + targetType + ";>;", "java/lang/Object",
new String[] { instantiatorType });
{
FieldVisitor fv =
cw.visitField(
ACC_FINAL,
"builderInstantiator",
"L" + AsmUtils.toAsmType(Instantiator.class) + ";",
"L" + AsmUtils.toAsmType(Instantiator.class) + "<Ljava/lang/Void;L"
+ AsmUtils.toAsmType(BiInstantiatorBuilder.getTargetType(instantiatorDefinition.getBuilderInstantiator())) + ";>;", null);
fv.visitEnd();
}
appendGetters(injections, cw);
appendInitBuilder(injections, cw, sourceType, classType, instantiatorDefinition);
appendNewInstanceBuilder(sourceClass, instantiatorDefinition, injections, cw, targetType, sourceType, classType, instantiatorDefinition.getSetters());
appendBridgeMethod(cw, targetType, sourceType, classType);
appendToString(injections, cw, instantiatorDefinition.getParameters());
cw.visitEnd();
return AsmUtils.writeClassToFile(className, cw.toByteArray());
}
private static <S> void appendGetters(Map<Parameter, Getter<? super S, ?>> injections, ClassWriter cw) {
FieldVisitor fv;
for(Entry<Parameter, Getter<? super S, ?>> entry : injections.entrySet()) {
GetterCall getterCall = getGetterCall(entry.getKey().getType(), entry.getValue().getClass());
fv = cw.visitField(ACC_FINAL, "getter_" + entry.getKey().getName(), AsmUtils.toTargetTypeDeclaration(getterCall.getterType), null, null);
fv.visitEnd();
}
}
private static <S> void appendInitBuilder(Map<Parameter, Getter<? super S, ?>> injections, ClassWriter cw, String sourceType, String classType, BuilderInstantiatorDefinition instantiatorDefinition) {
MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC, "<init>",
"(Ljava/util/Map;L" + AsmUtils.toAsmType(Instantiator.class) + ";)V",
"(Ljava/util/Map<Ljava.lang.String;L" + AsmUtils.toAsmType(Getter.class) +"<" + AsmUtils.toTargetTypeDeclaration(sourceType) + "*>;>;" +
"L" + AsmUtils.toAsmType(Instantiator.class) + "<Ljava/lang/Void;L" + AsmUtils.toAsmType(BiInstantiatorBuilder.getTargetType(instantiatorDefinition.getBuilderInstantiator())) +";>;)V", null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 2);
mv.visitFieldInsn(PUTFIELD, classType, "builderInstantiator", "L" + AsmUtils.toAsmType(Instantiator.class) + ";");
appendInitGetters(injections, classType, mv);
mv.visitInsn(RETURN);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private static <S> void appendInitGetters(Map<Parameter, Getter<? super S, ?>> injections, String classType, MethodVisitor mv) {
for(Entry<Parameter, Getter<? super S, ?>> entry : injections.entrySet()) {
String name = entry.getKey().getName();
GetterCall getterCall = getGetterCall(entry.getKey().getType(), entry.getValue().getClass());
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
mv.visitTypeInsn(CHECKCAST, AsmUtils.toAsmType(getterCall.getterType));
mv.visitFieldInsn(PUTFIELD, classType, "getter_" + name, AsmUtils.toTargetTypeDeclaration(getterCall.getterType));
}
}
private static <S> void appendInit(Map<Parameter, Getter<? super S, ?>> injections, ClassWriter cw, String sourceType, String classType) {
MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC, "<init>",
"(Ljava/util/Map;)V",
"(Ljava/util/Map<Ljava.lang.String;L" + AsmUtils.toAsmType(Getter.class) +"<" + AsmUtils.toTargetTypeDeclaration(sourceType) + "*>;>;)V", null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
appendInitGetters(injections, classType, mv);
mv.visitInsn(RETURN);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private static <S> void appendNewInstanceBuilder(Class<?> sourceClass, ExecutableInstantiatorDefinition instantiatorDefinition, Map<Parameter, Getter<? super S, ?>> injections, ClassWriter cw, String targetType, String sourceType, String classType, Parameter[] parameters) throws NoSuchMethodException {
MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "(" + AsmUtils.toTargetTypeDeclaration(sourceType) + ")" + AsmUtils.toTargetTypeDeclaration(targetType), null, new String[] { "java/lang/Exception" });
mv.visitCode();
mv.visitTypeInsn(NEW, targetType);
mv.visitInsn(DUP);
StringBuilder sb = new StringBuilder();
for (Parameter p : parameters) {
Getter<? super S, ?> getter = injections.get(p);
sb.append(AsmUtils.toTargetTypeDeclaration(p.getType()));
if (getter == null) {
if (TypeHelper.isPrimitive(p.getType())) {
mv.visitInsn(AsmUtils.defaultValue.get(p.getType()));
} else {
mv.visitInsn(ACONST_NULL);
}
} else {
invokeGetter(p, getter, classType, sourceClass, mv);
}
}
Member exec = instantiatorDefinition.getExecutable();
if (exec instanceof Constructor) {
mv.visitMethodInsn(INVOKESPECIAL, targetType, "<init>", "(" + sb.toString() + ")V", false);
} else {
mv.visitMethodInsn(INVOKESTATIC, AsmUtils.toAsmType(((Method)exec).getDeclaringClass()), exec.getName(),
AsmUtils.toSignature((Method)exec)
, false);
}
mv.visitInsn(ARETURN);
mv.visitMaxs(3 , 2);
mv.visitEnd();
}
private static <S> void invokeGetter(Parameter p, Getter<? super S, ?> getter, String classType, Class<?> sourceClass, MethodVisitor mv) throws NoSuchMethodException {
GetterCall getterCall = getGetterCall(p.getType(), getter.getClass());
String getterType = AsmUtils.toAsmType(getterCall.getterType);
String sourceType = AsmUtils.toAsmType(sourceClass.equals(Void.class) || sourceClass.equals(void.class) ? Object.class : sourceClass);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, classType, "getter_" + p.getName(), AsmUtils.toTargetTypeDeclaration(getterType));
mv.visitVarInsn(ALOAD, 1);
if (getterCall.isPrimitive) {
AsmUtils.invoke(mv, getterCall.getterType, getterCall.methodName, "(" + AsmUtils.toTargetTypeDeclaration(sourceType) + ")" + AsmUtils.toAsmType(p.getType()));
} else {
Class<?> wrapperClass = AsmUtils.toWrapperClass(p.getType());
Method getterMethod = BiInstantiatorBuilder.getMethod(TypeHelper.toClass(getterCall.getterType), "get", 1);
AsmUtils.invoke(mv, getterCall.getterType, getterMethod);
if (!wrapperClass.isAssignableFrom(getterMethod.getReturnType())) {
mv.visitTypeInsn(CHECKCAST, AsmUtils.toAsmType(wrapperClass));
}
if (TypeHelper.isPrimitive(p.getType())) {
String methodSuffix = getPrimitiveMethodSuffix(p);
String valueMethodPrefix = methodSuffix.toLowerCase();
if ("character".equals(valueMethodPrefix)) {
valueMethodPrefix = "char";
}
String valueMethod = valueMethodPrefix + "Value";
AsmUtils.invoke(mv, wrapperClass, valueMethod, "()" + AsmUtils.toAsmType(p.getType()));
}
}
}
private static String getPrimitiveMethodSuffix(Parameter p) {
return getPrimitiveMethodSuffix(p.getType());
}
private static String getPrimitiveMethodSuffix(Class<?> type) {
String methodSuffix = AsmUtils.wrappers.get(type).getSimpleName();
if ("Integer".equals(methodSuffix)) {
methodSuffix = "Int";
}
return methodSuffix;
}
private static <S> void appendNewInstanceBuilder(Class<?> sourceClass, BuilderInstantiatorDefinition instantiatorDefinition, Map<Parameter, Getter<? super S, ?>> injections, ClassWriter cw, String targetType, String sourceType, String classType, Map<Parameter, Method> setters) throws NoSuchMethodException {
MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "(" + AsmUtils.toTargetTypeDeclaration(sourceType) + ")" + AsmUtils.toTargetTypeDeclaration(targetType), null, new String[] { "java/lang/Exception" });
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD,classType, "builderInstantiator", AsmUtils.toTargetTypeDeclaration(Instantiator.class));
mv.visitInsn(ACONST_NULL);
mv.visitMethodInsn(INVOKEINTERFACE, AsmUtils.toAsmType(Instantiator.class), "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
final Type builderClass = BiInstantiatorBuilder.getTargetType(instantiatorDefinition.getBuilderInstantiator());
final String builderType = AsmUtils.toAsmType(builderClass);
mv.visitTypeInsn(CHECKCAST, builderType);
mv.visitVarInsn(ASTORE, 2);
for (Entry<Parameter, Method> e : setters.entrySet()) {
mv.visitVarInsn(ALOAD, 2);
Parameter p = e.getKey();
Getter<? super S, ?> getter = injections.get(p);
if (getter == null) {
if (TypeHelper.isPrimitive(p.getType())) {
mv.visitInsn(AsmUtils.defaultValue.get(p.getType()));
} else {
mv.visitInsn(ACONST_NULL);
}
} else {
invokeGetter(p, getter, classType, sourceClass, mv);
AsmUtils.invoke(mv, TypeHelper.toClass(builderClass), e.getValue().getName(),
AsmUtils.toSignature(e.getValue()));
if (!Void.TYPE.equals(e.getValue().getReturnType())) {
mv.visitVarInsn(ASTORE, 2);
}
}
}
mv.visitVarInsn(ALOAD, 2);
AsmUtils.invoke(mv, TypeHelper.toClass(builderClass),
instantiatorDefinition.getBuildMethod().getName(),
AsmUtils.toSignature(instantiatorDefinition.getBuildMethod()));
mv.visitInsn(ARETURN);
mv.visitMaxs(3 , 2);
mv.visitEnd();
}
private static GetterCall getGetterCall(Class<?> propertyType, Class<? extends Getter> getterClass) {
if (TypeHelper.isPrimitive(propertyType)) {
Class<?> primitiveGetter = BiInstantiatorBuilder.getPrimitiveGetter(propertyType);
if (primitiveGetter == null) {
throw new IllegalStateException("No primitive getter for primitive " + propertyType);
}
Type publicGetterClass = AsmUtils.findClosestPublicTypeExposing(getterClass, primitiveGetter);
if (publicGetterClass != null) {
return new GetterCall("get" + getPrimitiveMethodSuffix(propertyType), publicGetterClass, true);
}
}
Type publicGetterClass = AsmUtils.findClosestPublicTypeExposing(getterClass, Getter.class);
return new GetterCall("get", publicGetterClass, false);
}
private static void appendBridgeMethod(ClassWriter cw, String targetType, String sourceType, String classType) {
MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
"newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", null,
new String[] { "java/lang/Exception" });
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, sourceType);
mv.visitMethodInsn(INVOKEVIRTUAL, classType, "newInstance", "(" + AsmUtils.toTargetTypeDeclaration(sourceType) + ")"
+ AsmUtils.toTargetTypeDeclaration(targetType), false);
mv.visitInsn(ARETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
private static <S> void appendToString(Map<Parameter, Getter<? super S, ?>> injections, ClassWriter cw, Parameter[] parameters) {
MethodVisitor mv;
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);
for(int i = 0; i < parameters.length; i++) {
String paramName = (i > 0 ? ", " : "") + "parameter" + i + "=";
String parameter = String.valueOf(parameters[i]);
mv.visitLdcInsn(paramName);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn(parameter);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
Getter<? super S, ?> getter = injections.get(parameters[i]);
String getterName = ", parameter" + i + "=";
String getterString = String.valueOf(getter);
mv.visitLdcInsn(getterName);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn(getterString);
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();
}
static class GetterCall {
final String methodName;
final Type getterType;
final boolean isPrimitive;
GetterCall(String methodName, Type getterType, boolean isPrimitive) {
this.methodName = methodName;
this.getterType = getterType;
this.isPrimitive = isPrimitive;
}
}
}