package openmods.reflection;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import openmods.utils.CachedFactory;
import openmods.utils.SneakyThrower;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;
public class ClonerFactory implements Opcodes {
private static class ClonerClassLoader extends ClassLoader {
private ClonerClassLoader() {
super(ClonerClassLoader.class.getClassLoader());
}
public Class<?> define(byte[] data) {
return defineClass(null, data, 0, data.length);
}
}
public interface ICloner<T> {
public <A extends T, B extends T> void clone(A from, B to);
}
private static final String CLONER_DESC = Type.getInternalName(ICloner.class);
private static final Method CLONER_FUNC_DESC = Method.getMethod(ICloner.class.getDeclaredMethods()[0]);
public static final ClonerFactory instance = new ClonerFactory();
private final CachedFactory<Class<?>, ICloner<?>> cache = new CachedFactory<Class<?>, ClonerFactory.ICloner<?>>() {
@Override
protected ICloner<?> create(Class<?> key) {
try {
Class<? extends ICloner<?>> cls = createClonerClass(key);
return cls.newInstance();
} catch (Throwable t) {
throw SneakyThrower.sneakyThrow(t);
}
}
};
private final ClonerClassLoader clonerClassLoader = new ClonerClassLoader();
private static byte[] createClonerClassData(Class<?> cls) {
final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
final String commonCls = Type.getInternalName(cls);
final String name = commonCls + "$$cloner$";
writer.visit(V1_6, ACC_PUBLIC | ACC_SUPER | ACC_SYNTHETIC, name, null, "java/lang/Object", new String[] { CLONER_DESC });
writer.visitSource(".dynamic", null);
{
MethodVisitor mv = writer.visitMethod(ACC_PUBLIC | ACC_SYNTHETIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
{
MethodVisitor mv = writer.visitMethod(ACC_PUBLIC | ACC_SYNTHETIC, CLONER_FUNC_DESC.getName(), CLONER_FUNC_DESC.getDescriptor(), null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitTypeInsn(Opcodes.CHECKCAST, commonCls);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.CHECKCAST, commonCls);
Class<?> currentCls = cls;
while (currentCls != Object.class) {
addClonedFields(mv, currentCls);
currentCls = currentCls.getSuperclass();
}
mv.visitInsn(Opcodes.POP2);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
writer.visitEnd();
return writer.toByteArray();
}
private static void addClonedFields(MethodVisitor writer, Class<?> currentCls) {
final String owner = Type.getType(currentCls).getInternalName();
for (Field f : currentCls.getDeclaredFields()) {
final int modifier = f.getModifiers();
if (Modifier.isFinal(modifier) || Modifier.isStatic(modifier) || !Modifier.isPublic(modifier)) continue;
final String fieldDesc = Type.getType(f.getType()).getDescriptor();
writer.visitInsn(Opcodes.DUP2);
writer.visitFieldInsn(Opcodes.GETFIELD, owner, f.getName(), fieldDesc);
writer.visitFieldInsn(Opcodes.PUTFIELD, owner, f.getName(), fieldDesc);
}
}
@SuppressWarnings("unchecked")
private Class<? extends ICloner<?>> createClonerClass(Class<?> cls) {
final byte[] classData = createClonerClassData(cls);
return (Class<? extends ICloner<?>>)clonerClassLoader.define(classData);
}
@SuppressWarnings("unchecked")
public <T> ICloner<T> getCloner(Class<T> cls) {
return (ICloner<T>)cache.getOrCreate(cls);
}
}