package de.take_weiland.mods.commons.internal.sync;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.primitives.UnsignedBytes;
import de.take_weiland.mods.commons.asm.ASMUtils;
import de.take_weiland.mods.commons.net.MCDataInput;
import de.take_weiland.mods.commons.net.MCDataOutput;
import de.take_weiland.mods.commons.reflect.SCReflection;
import de.take_weiland.mods.commons.serialize.Property;
import de.take_weiland.mods.commons.sync.Syncer;
import de.take_weiland.mods.commons.util.UnsignedShorts;
import net.minecraft.entity.Entity;
import net.minecraft.inventory.Container;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.IExtendedEntityProperties;
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 org.objectweb.asm.commons.TableSwitchGenerator;
import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.Map;
import static java.lang.invoke.MethodType.methodType;
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.Opcodes.*;
import static org.objectweb.asm.Type.*;
import static org.objectweb.asm.commons.GeneratorAdapter.NE;
import static org.objectweb.asm.commons.Method.getMethod;
/**
* @author diesieben07
*/
public final class BytecodeEmittingCompanionGenerator {
private static final String SYNCER = "syn";
private static final String COMPANION = "com";
private static final String GETTER = "get";
private static final String SETTER = "set";
private final DefaultCompanionFactory factory;
private final Class<?> clazz;
private String className;
private String superName;
private Class<?> superClass;
private ClassWriter cw;
private final Map<Property<?, ?>, Syncer<?, ?>> properties;
private int firstID;
BytecodeEmittingCompanionGenerator(DefaultCompanionFactory factory, Class<?> clazz, Map<Property<?, ?>, Syncer<?, ?>> properties) {
this.factory = factory;
this.clazz = clazz;
this.properties = properties;
}
Class<?> generateCompanion() {
beginClass();
makeFields();
makeCLInit();
makeReadID();
makeRead();
makeWriteID();
makeCheck();
return finish();
}
private void beginClass() {
className = SCReflection.nextDynamicClassName(BytecodeEmittingCompanionGenerator.class.getPackage());
superClass = findAppropriateSuperClass();
superName = Type.getInternalName(superClass);
firstID = factory.getNextFreeIDFor(clazz);
cw = new ClassWriter(COMPUTE_FRAMES);
cw.visit(V1_7, ACC_PUBLIC, className, null, superName, null);
Method cstr = getMethod("void <init>()");
GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC, cstr, null, null, cw);
gen.visitCode();
gen.loadThis();
gen.invokeConstructor(Type.getObjectType(superName), cstr);
gen.returnValue();
gen.endMethod();
}
private Class<?> findAppropriateSuperClass() {
Class<?> superClassCompanion = factory.getCompanionClass(clazz.getSuperclass());
if (superClassCompanion == null) {
return isIEEP() ? IEEPSyncCompanion.class : SyncCompanion.class;
} else {
return superClassCompanion;
}
}
private boolean isIEEP() {
return IExtendedEntityProperties.class.isAssignableFrom(clazz);
}
private void makeFields() {
for (Map.Entry<Property<?, ?>, Syncer<?, ?>> entry : properties.entrySet()) {
Property<?, ?> property = entry.getKey();
Syncer<?, ?> syncer = entry.getValue();
String descSyncer = Type.getDescriptor(Syncer.class);
String descMH = Type.getDescriptor(MethodHandle.class);
cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, getPropertyID(property, SYNCER), descSyncer, null, null);
cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, getPropertyID(property, GETTER), descMH, null, null);
cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, getPropertyID(property, SETTER), descMH, null, null);
Class<?> companionType = syncer.getCompanionType();
if (companionType != null) {
cw.visitField(ACC_PRIVATE, getPropertyID(property, COMPANION), Type.getDescriptor(companionType), null, null);
}
}
}
private void makeRead() {
Method method = getMethod("int read(Object, de.take_weiland.mods.commons.net.MCDataInput)");
final GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC, method, null, null, cw);
gen.visitCode();
final Type myType = Type.getObjectType(className);
final Type mcDataInType = Type.getType(MCDataInput.class);
final Type syncerType = Type.getType(Syncer.class);
final Type objectType = Type.getType(Object.class);
final Type valueHolderType = Type.getType(clazz);
final int objectArg = 0;
final int inStreamArg = 1;
final int fieldID = gen.newLocal(Type.INT_TYPE);
if (needCallSuper()) {
gen.loadThis();
gen.loadArg(0);
gen.loadArg(1);
gen.invokeConstructor(Type.getObjectType(superName), method);
gen.storeLocal(fieldID);
} else {
gen.loadThis();
gen.loadArg(inStreamArg);
gen.invokeVirtual(myType, readIDMethod());
gen.storeLocal(fieldID);
}
Label start = gen.mark();
int keyCount = properties.size();
int[] keys = new int[keyCount];
for (int i = 0; i < keyCount; i++) {
keys[i] = i + firstID;
}
gen.loadLocal(fieldID);
gen.tableSwitch(keys, new TableSwitchGenerator() {
@Override
public void generateCase(int key, Label end) {
Map.Entry<Property<?, ?>, Syncer<?, ?>> entry = Iterables.get(properties.entrySet(), key - firstID);
Property<?, ?> property = entry.getKey();
Syncer<?, ?> syncer = entry.getValue();
boolean hasCompanion = syncer.getCompanionType() != null;
Type companionType = hasCompanion ? Type.getType(syncer.getCompanionType()) : null;
prepareSetValue(gen, property);
gen.getStatic(myType, getPropertyID(property, SYNCER), syncerType);
loadValue(gen, property);
ASMUtils.convertTypes(gen, property.getRawType(), Object.class);
if (hasCompanion) {
gen.loadThis();
gen.getField(myType, getPropertyID(property, COMPANION), companionType);
ASMUtils.convertTypes(gen, syncer.getCompanionType(), Object.class);
} else {
gen.push((String) null); // type doesn't matter
}
gen.loadArg(inStreamArg);
gen.invokeInterface(syncerType, new Method("read", objectType, new Type[]{objectType, objectType, mcDataInType}));
ASMUtils.convertTypes(gen, Object.class, property.getRawType());
doSetValue(gen, property);
gen.goTo(end);
}
@Override
public void generateDefault() {
gen.loadLocal(fieldID); // unknown ID, either return control back to super caller or end of stream (-1)
gen.returnValue();
}
});
gen.loadThis();
gen.loadArg(inStreamArg);
gen.invokeVirtual(myType, readIDMethod());
gen.storeLocal(fieldID);
gen.goTo(start);
gen.endMethod();
}
private void loadValue(GeneratorAdapter gen, Property<?, ?> property) {
Type myType = Type.getObjectType(className);
Type mhType = Type.getType(MethodHandle.class);
Type propertyType = Type.getType(property.getRawType());
Type objectType = Type.getType(Object.class);
gen.getStatic(myType, getPropertyID(property, GETTER), mhType);
gen.loadArg(0);
gen.invokeVirtual(mhType, new Method("invokeExact", propertyType, new Type[]{objectType}));
}
private void prepareSetValue(GeneratorAdapter gen, Property<?, ?> property) {
Type myType = Type.getObjectType(className);
Type mhType = Type.getType(MethodHandle.class);
gen.getStatic(myType, getPropertyID(property, SETTER), mhType);
gen.loadArg(0);
}
private void doSetValue(GeneratorAdapter gen, Property<?, ?> property) {
Type mhType = Type.getType(MethodHandle.class);
Type propertyType = Type.getType(property.getRawType());
Type objectType = Type.getType(Object.class);
gen.invokeVirtual(mhType, new Method("invokeExact", Type.VOID_TYPE, new Type[] { objectType, propertyType }));
}
private static Method readIDMethod() {
return getMethod("int readID(de.take_weiland.mods.commons.net.MCDataInput)");
}
private void makeReadID() {
Method method = readIDMethod();
GeneratorAdapter gen = new GeneratorAdapter(0, method, null, null, cw);
gen.visitCode();
Method readMethod = new Method("read" + idSize(true), INT_TYPE, new Type[0]);
gen.loadArg(0);
gen.invokeInterface(Type.getType(MCDataInput.class), readMethod);
gen.returnValue();
gen.endMethod();
}
private String idSize(boolean read) {
int maxId = firstID + properties.size() - 1;
if (maxId <= UnsignedBytes.toInt(UnsignedBytes.MAX_VALUE)) {
return read ? "UnsignedByte" : "Byte";
} else if (maxId <= UnsignedShorts.MAX_VALUE) {
return read ? "UnsignedShort" : "Short";
} else {
return "Int";
}
}
private static Method writeIDMethod() {
return getMethod("void writeID(de.take_weiland.mods.commons.net.MCDataOutput, int)");
}
private void makeWriteID() {
Method method = writeIDMethod();
GeneratorAdapter gen = new GeneratorAdapter(0, method, null, null, cw);
gen.visitCode();
Method writeMethod = new Method("write" + idSize(false), VOID_TYPE, new Type[] { INT_TYPE });
gen.loadArg(0);
gen.loadArg(1);
gen.invokeInterface(Type.getType(MCDataOutput.class), writeMethod);
gen.returnValue();
gen.endMethod();
}
private void makeCheck() {
Method method = getMethod("de.take_weiland.mods.commons.net.MCDataOutput check(Object, boolean)");
GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC, method, null, null, cw);
gen.visitCode();
Type mcDataOutType = Type.getType(MCDataOutput.class);
Type syncHelpersType = Type.getType(SyncHelpers.class);
Type myType = Type.getObjectType(className);
Type valueHolderType = Type.getType(clazz);
Type entityType = Type.getType(Entity.class);
Type tileEntityType = Type.getType(TileEntity.class);
Type containerType = Type.getType(Container.class);
Type ieepCompType = Type.getType(IEEPSyncCompanion.class);
Type syncerType = Type.getType(Syncer.class);
int objectArg = 0;
int superCallArg = 1;
int outStreamID = gen.newLocal(mcDataOutType);
if (needCallSuper()) {
gen.loadThis();
gen.loadArg(0);
gen.push(true); // isSuperCall
gen.invokeConstructor(getObjectType(superName), method);
} else {
gen.push((String) null); // don't care about the type
}
gen.storeLocal(outStreamID);
Label next = null;
SyncType syncType = SyncHelpers.getSyncType(clazz);
int index = 0;
for (Map.Entry<Property<?, ?>, Syncer<?, ?>> entry : properties.entrySet()) {
Property<?, ?> property = entry.getKey();
Syncer<?, ?> syncer = entry.getValue();
if (next != null) {
gen.mark(next);
}
next = new Label();
boolean hasCompanion = syncer.getCompanionType() != null;
Type companionType = hasCompanion ? Type.getType(syncer.getCompanionType()) : null;
gen.getStatic(myType, getPropertyID(property, SYNCER), syncerType);
loadValue(gen, property);
ASMUtils.convertTypes(gen, property.getRawType(), Object.class);
if (hasCompanion) {
gen.loadThis();
gen.getField(myType, getPropertyID(property, COMPANION), companionType);
ASMUtils.convertTypes(gen, syncer.getCompanionType(), Object.class);
} else {
gen.push((String) null); // type doesnt matter
}
gen.invokeInterface(syncerType, getMethod("boolean equal(Object, Object)"));
gen.ifZCmp(NE, next);
Label nonNull = new Label();
gen.loadLocal(outStreamID);
gen.ifNonNull(nonNull);
switch (syncType) {
case ENTITY:
gen.loadArg(0);
gen.checkCast(entityType);
gen.invokeStatic(syncHelpersType, new Method("newOutStream", mcDataOutType, new Type[] { entityType }));
break;
case TILE_ENTITY:
gen.loadArg(0);
gen.checkCast(tileEntityType);
gen.invokeStatic(syncHelpersType, new Method("newOutStream", mcDataOutType, new Type[] { tileEntityType }));
break;
case CONTAINER:
gen.loadArg(0);
gen.checkCast(containerType);
gen.invokeStatic(syncHelpersType, new Method("newOutStream", mcDataOutType, new Type[] { containerType }));
break;
case ENTITY_PROPS:
gen.loadThis();
gen.invokeStatic(syncHelpersType, new Method("newOutStream", mcDataOutType, new Type[] { ieepCompType }));
break;
}
gen.storeLocal(outStreamID);
gen.mark(nonNull);
gen.loadThis();
gen.loadLocal(outStreamID);
gen.push(firstID + index);
gen.invokeVirtual(myType, writeIDMethod());
if (hasCompanion) {
gen.loadThis(); // for companion set
}
gen.getStatic(myType, getPropertyID(property, SYNCER), syncerType);
loadValue(gen, property);
ASMUtils.convertTypes(gen, property.getRawType(), Object.class);
if (hasCompanion) {
gen.loadThis();
gen.getField(myType, getPropertyID(property, COMPANION), companionType);
ASMUtils.convertTypes(gen, syncer.getCompanionType(), Object.class);
} else {
gen.push((String) null);
}
gen.loadLocal(outStreamID);
gen.invokeInterface(syncerType, getMethod("Object writeAndUpdate(Object, Object, de.take_weiland.mods.commons.net.MCDataOutput)"));
if (hasCompanion) {
ASMUtils.convertTypes(gen, Object.class, syncer.getCompanionType());
gen.putField(myType, getPropertyID(property, COMPANION), companionType);
} else {
gen.pop();
}
}
if (next != null) {
gen.mark(next);
}
Label end = new Label();
gen.loadArg(1);
gen.ifZCmp(NE, end);
gen.loadLocal(outStreamID);
gen.ifNull(end);
gen.loadThis();
gen.loadLocal(outStreamID);
gen.push(0);
gen.invokeVirtual(myType, writeIDMethod());
switch (syncType) {
case ENTITY:
gen.loadArg(0);
gen.checkCast(entityType);
gen.loadLocal(outStreamID);
gen.invokeStatic(syncHelpersType, new Method("sendStream", VOID_TYPE, new Type[] { entityType, mcDataOutType }));
break;
case TILE_ENTITY:
gen.loadArg(0);
gen.checkCast(tileEntityType);
gen.loadLocal(outStreamID);
gen.invokeStatic(syncHelpersType, new Method("sendStream", VOID_TYPE, new Type[] { tileEntityType, mcDataOutType }));
break;
case CONTAINER:
gen.loadArg(0);
gen.checkCast(containerType);
gen.loadLocal(outStreamID);
gen.invokeStatic(syncHelpersType, new Method("sendStream", VOID_TYPE, new Type[] { containerType, mcDataOutType }));
break;
case ENTITY_PROPS:
gen.loadThis();
gen.getField(ieepCompType, "_sc$entity", entityType);
gen.loadLocal(outStreamID);
gen.invokeStatic(syncHelpersType, new Method("sendStream", VOID_TYPE, new Type[] { entityType, mcDataOutType }));
break;
}
gen.mark(end);
gen.loadLocal(outStreamID);
gen.returnValue();
gen.endMethod();
}
private boolean needCallSuper() {
return superClass != SyncCompanion.class && superClass != IEEPSyncCompanion.class;
}
private void makeCLInit() {
GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC | ACC_STATIC, getMethod("void <clinit>()"), null, null, cw);
gen.visitCode();
Type myType = getObjectType(className);
Type iteratorType = Type.getType(Iterator.class);
Type objectArrType = Type.getType(Object[].class);
Type objectType = Type.getType(Object.class);
Type methodHandleType = Type.getType(MethodHandle.class);
Type syncerType = Type.getType(Syncer.class);
int iterator = gen.newLocal(iteratorType);
gen.invokeStatic(Type.getType(BytecodeEmittingCompanionGenerator.class), getMethod("java.util.Iterator getStaticData()"));
gen.storeLocal(iterator);
for (Property<?, ?> property : properties.keySet()) {
gen.loadLocal(iterator);
gen.invokeInterface(iteratorType, getMethod("Object next()"));
gen.checkCast(objectArrType);
gen.dup();
gen.push(0);
gen.arrayLoad(objectType);
gen.checkCast(syncerType);
gen.putStatic(myType, getPropertyID(property, SYNCER), syncerType);
gen.dup();
gen.push(1);
gen.arrayLoad(objectType);
gen.checkCast(methodHandleType);
gen.putStatic(myType, getPropertyID(property, GETTER), methodHandleType);
gen.push(2);
gen.arrayLoad(objectType);
gen.checkCast(methodHandleType);
gen.putStatic(myType, getPropertyID(property, SETTER), methodHandleType);
}
gen.returnValue();
gen.endMethod();
}
private static String getPropertyID(Property<?, ?> property, String role) {
return getPropertyID(property) + "$" + role;
}
private static String getPropertyID(Property<?, ?> property) {
return property.getName() + (property.getMember() instanceof Field ? "$f" : "$m");
}
private static Map<Property<?, ?>, Syncer<?, ?>> staticProperties;
private Class<?> finish() {
Class<?> cls;
// gross hack, please close your eyes
synchronized (BytecodeEmittingCompanionGenerator.class) {
// <clinit> of generated class calls getMHMap
staticProperties = properties;
try {
cw.visitEnd();
cls = SCReflection.defineDynamicClass(cw.toByteArray(), BytecodeEmittingCompanionGenerator.class);
} finally{
staticProperties = null;
}
}
return cls;
}
// called from <clinit> in generated classes, see #finish()
@SuppressWarnings("unused")
static Iterator<Object[]> getStaticData() throws NoSuchFieldException, IllegalAccessException {
return FluentIterable.from(staticProperties.entrySet())
.transform(new Function<Map.Entry<Property<?,?>,Syncer<?,?>>, Object[]>() {
@Nullable
@Override
public Object[] apply(Map.Entry<Property<?, ?>, Syncer<?, ?>> entry) {
Property<?, ?> property = entry.getKey();
Syncer<?, ?> syncer = entry.getValue();
return new Object[] {
syncer,
property.getGetter().asType(methodType(property.getRawType(), Object.class)),
property.getSetter().asType(methodType(void.class, Object.class, property.getRawType()))
};
}
})
.iterator();
}
}