package de.take_weiland.mods.commons.internal.tonbt; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import de.take_weiland.mods.commons.asm.ASMUtils; import de.take_weiland.mods.commons.nbt.NBTData; import de.take_weiland.mods.commons.nbt.NBTSerializer; import de.take_weiland.mods.commons.reflect.SCReflection; import de.take_weiland.mods.commons.serialize.Property; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; import java.lang.invoke.MethodHandle; import java.lang.reflect.Member; import java.util.Iterator; import java.util.List; import static java.lang.invoke.MethodType.methodType; import static org.objectweb.asm.Opcodes.*; import static org.objectweb.asm.Type.BOOLEAN_TYPE; import static org.objectweb.asm.Type.VOID_TYPE; import static org.objectweb.asm.commons.GeneratorAdapter.NE; import static org.objectweb.asm.commons.Method.getMethod; /** * @author diesieben07 */ final class BytecodeEmittingHandlerGenerator { private static final String SERIALIZER = "ser"; private static final String GETTER = "get"; private static final String SETTER = "set"; private final DefaultHandlerFactory factory; private final Class<?> clazz; private ClassWriter cw; private String className; private String superName; private List<Property<?, ?>> properties; BytecodeEmittingHandlerGenerator(DefaultHandlerFactory factory, Class<?> clazz) { this.factory = factory; this.clazz = clazz; } Class<? extends ToNbtHandler> generateHandler() { properties = ToNbtFactories.getProperties(clazz); if (properties.isEmpty()) { return null; } newClassWriter(); genFields(); genCLInit(); genWrite(); genRead(); return finish(); } private void newClassWriter() { className = SCReflection.nextDynamicClassName(BytecodeEmittingHandlerGenerator.class.getPackage()); superName = chooseSuperName(); cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cw.visit(V1_7, 0, className, null, superName, null); GeneratorAdapter gen = new GeneratorAdapter(0, getMethod("void <init>()"), null, null, cw); gen.loadThis(); gen.invokeConstructor(Type.getObjectType(superName), getMethod("void <init>()")); gen.returnValue(); gen.endMethod(); } private String chooseSuperName() { Class<?> superHClass = factory.getHandlerClass(clazz.getSuperclass()); return Type.getInternalName(superHClass == null ? ToNbtHandler.class : superHClass); } private boolean needCallSuper() { return !superName.equals(Type.getInternalName(ToNbtHandler.class)); } private void genFields() { String nbtSerializerDesc = Type.getDescriptor(NBTSerializer.class); String methodHandleDesc = Type.getDescriptor(MethodHandle.class); for (Property<?, ?> property : properties) { cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, identFor(property, SERIALIZER), nbtSerializerDesc, null, null).visitEnd(); cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, identFor(property, GETTER), methodHandleDesc, null, null).visitEnd(); cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, identFor(property, SETTER), methodHandleDesc, null, null).visitEnd(); } } private void genCLInit() { Type iterType = Type.getType(Iterator.class); Type objectArrType = Type.getType(Object[].class); Type objectType = Type.getType(Object.class); Type nbtSerType = Type.getType(NBTSerializer.class); Type generatorType = Type.getType(BytecodeEmittingHandlerGenerator.class); Type methodHandleType = Type.getType(MethodHandle.class); Type myType = Type.getObjectType(className); Method iterNext = getMethod("Object next()"); GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC | ACC_STATIC, getMethod("void <clinit>()"), null, null, cw); gen.visitCode(); int iterLocal = gen.newLocal(iterType); gen.invokeStatic(generatorType, new Method("getStaticInfo", iterType, new Type[0])); gen.storeLocal(iterLocal); for (Property<?, ?> property : properties) { gen.loadLocal(iterLocal); gen.invokeInterface(iterType, iterNext); gen.checkCast(objectArrType); gen.dup(); gen.push(0); gen.arrayLoad(objectType); gen.checkCast(nbtSerType); gen.putStatic(myType, identFor(property, SERIALIZER), nbtSerType); gen.dup(); gen.push(1); gen.arrayLoad(objectType); gen.checkCast(methodHandleType); gen.putStatic(myType, identFor(property, GETTER), methodHandleType); gen.push(2); gen.arrayLoad(objectType); gen.checkCast(methodHandleType); gen.putStatic(myType, identFor(property, SETTER), methodHandleType); } gen.returnValue(); gen.endMethod(); } private void genWrite() { Method method = getMethod("void write(Object, net.minecraft.nbt.NBTTagCompound)"); GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC, method, null, null, cw); gen.visitCode(); if (needCallSuper()) { gen.loadThis(); gen.loadArg(0); gen.loadArg(1); gen.invokeConstructor(Type.getObjectType(superName), method); } Type myType = Type.getObjectType(className); Type serializerType = Type.getType(NBTSerializer.class); Type nbtBaseType = Type.getType(NBTBase.class); Type stringType = Type.getType(String.class); Type nbtCompType = Type.getType(NBTTagCompound.class); Type objectType = Type.getType(Object.class); Type nbtDataType = Type.getType(NBTData.class); Method serializerWrite = getMethod("net.minecraft.nbt.NBTBase write(Object)"); Method nbtCompoundSet = new Method(/* MCPNames.method(MCPNames.M_SET_TAG)*/ "setTag", Type.VOID_TYPE, new Type[] { stringType, nbtBaseType }); Method serializedNull = new Method("serializedNull", nbtBaseType, new Type[0]); int valueSlot = -1; for (Property<?, ?> property : properties) { gen.loadArg(1); gen.push(property.getName()); boolean isPrimitive = property.getRawType().isPrimitive(); if (isPrimitive) { gen.getStatic(myType, identFor(property, SERIALIZER), serializerType); getValue(gen, property); ASMUtils.convertTypes(gen, property.getRawType(), Object.class); gen.invokeInterface(serializerType, serializerWrite); } else { if (valueSlot == -1) { valueSlot = gen.newLocal(objectType); } getValue(gen, property); ASMUtils.convertTypes(gen, property.getRawType(), Object.class); gen.storeLocal(valueSlot); gen.loadLocal(valueSlot); Label notNull = new Label(); Label end = new Label(); gen.ifNonNull(notNull); gen.invokeStatic(nbtDataType, serializedNull); gen.goTo(end); gen.mark(notNull); gen.getStatic(myType, identFor(property, SERIALIZER), serializerType); gen.loadLocal(valueSlot); gen.invokeInterface(serializerType, serializerWrite); gen.mark(end); } gen.invokeVirtual(nbtCompType, nbtCompoundSet); } gen.returnValue(); gen.endMethod(); } private void genRead() { Method method = getMethod("void read(Object, net.minecraft.nbt.NBTTagCompound)"); GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC, method, null, null, cw); gen.visitCode(); if (needCallSuper()) { gen.loadThis(); gen.loadArg(0); gen.loadArg(1); gen.invokeConstructor(Type.getObjectType(superName), method); } Type myType = Type.getObjectType(className); Type serializerType = Type.getType(NBTSerializer.class); Type nbtCompType = Type.getType(NBTTagCompound.class); Type nbtBaseType = Type.getType(NBTBase.class); Type stringType = Type.getType(String.class); Type nbtDataType = Type.getType(NBTData.class); Type objectType = Type.getType(Object.class); Method nbtCompGetTag = new Method("getTag", nbtBaseType, new Type[] { stringType }); Method serializerRead = new Method("read", objectType, new Type[] { objectType, nbtBaseType }); Method isSerNull = new Method("isSerializedNull", BOOLEAN_TYPE, new Type[] { nbtBaseType }); final int nbtTagSlot = gen.newLocal(nbtBaseType); for (Property<?, ?> property : properties) { prepareSetValue(gen, property); gen.loadArg(1); gen.push(property.getName()); gen.invokeVirtual(nbtCompType, nbtCompGetTag); gen.storeLocal(nbtTagSlot); Label isNull = new Label(); Label end = new Label(); gen.loadLocal(nbtTagSlot); gen.invokeStatic(nbtDataType, isSerNull); gen.ifZCmp(NE, isNull); gen.getStatic(myType, identFor(property, SERIALIZER), serializerType); getValue(gen, property); ASMUtils.convertTypes(gen, property.getRawType(), Object.class); gen.loadLocal(nbtTagSlot); gen.invokeInterface(serializerType, serializerRead); ASMUtils.convertTypes(gen, Object.class, property.getRawType()); gen.goTo(end); gen.mark(isNull); pushDefaultValue(gen, property.getRawType()); gen.mark(end); doSetValue(gen, property); } gen.returnValue(); gen.endMethod(); } private void pushDefaultValue(GeneratorAdapter gen, Class<?> type) { if (type.isPrimitive()) { switch (Type.getType(type).getSort()) { case Type.FLOAT: gen.push(0f); break; case Type.DOUBLE: gen.push(0d); break; default: gen.push(0); break; } } else { gen.push((String) null); } } private void getValue(GeneratorAdapter gen, Property<?, ?> property) { Type myType = Type.getObjectType(className); Type methodHandleType = Type.getType(MethodHandle.class); Type propertyType = Type.getType(property.getRawType()); Type objectType = Type.getType(Object.class); gen.getStatic(myType, identFor(property, GETTER), methodHandleType); gen.loadArg(0); gen.invokeVirtual(methodHandleType, new Method("invokeExact", propertyType, new Type[]{objectType})); } private void prepareSetValue(GeneratorAdapter gen, Property<?, ?> property) { Type myType = Type.getObjectType(className); Type methodHandleType = Type.getType(MethodHandle.class); gen.getStatic(myType, identFor(property, SETTER), methodHandleType); gen.loadArg(0); } private void doSetValue(GeneratorAdapter gen, Property<?, ?> property) { Type propertyType = Type.getType(property.getRawType()); Type objectType = Type.getType(Object.class); Type methodHandleType = Type.getType(MethodHandle.class); gen.invokeVirtual(methodHandleType, new Method("invokeExact", VOID_TYPE, new Type[] { objectType, propertyType })); } private Class<? extends ToNbtHandler> finish() { cw.visitEnd(); Class<? extends ToNbtHandler> result; synchronized (BytecodeEmittingHandlerGenerator.class) { staticProperties = properties; //noinspection unchecked result = (Class<? extends ToNbtHandler>) SCReflection.defineDynamicClass(cw.toByteArray()); staticProperties = null; } return result; } private static List<Property<?, ?>> staticProperties; @SuppressWarnings("unused") // called by the dynamic classes static Iterator<Object[]> getStaticInfo() { return FluentIterable.from(staticProperties) .transform(new Function<Property<?, ?>, Object[]>() { @Override public Object[] apply(Property<?, ?> property) { Class<?> rawType = property.getRawType(); return new Object[]{ ToNbtFactories.serializerFor(property), property.getGetter().asType(methodType(rawType, Object.class)), property.getSetter().asType(methodType(void.class, Object.class, rawType)) }; } }) .iterator(); } private String identFor(Property<?, ?> property, String type) { Member member = property.getMember(); return "_sc$" + (member instanceof java.lang.reflect.Method ? "m$" : "") + member.getName() + '$' + type; } }