test fixes, javadoc
This commit is contained in:
parent
152c3469d9
commit
3e636667a9
4 changed files with 81 additions and 41 deletions
|
|
@ -7,11 +7,13 @@ import com.github.shautvast.rusty.Result;
|
||||||
import org.objectweb.asm.ClassWriter;
|
import org.objectweb.asm.ClassWriter;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
|
import static com.github.shautvast.reflective.java.Java.*;
|
||||||
|
import static com.github.shautvast.reflective.java.Java.DOUBLE;
|
||||||
|
import static com.github.shautvast.reflective.java.Java.FLOAT;
|
||||||
|
import static com.github.shautvast.reflective.java.Java.INTEGER;
|
||||||
|
import static com.github.shautvast.reflective.java.Java.LONG;
|
||||||
import static com.github.shautvast.rusty.Result.err;
|
import static com.github.shautvast.rusty.Result.err;
|
||||||
import static com.github.shautvast.rusty.Result.ok;
|
import static com.github.shautvast.rusty.Result.ok;
|
||||||
import static org.objectweb.asm.Opcodes.*;
|
import static org.objectweb.asm.Opcodes.*;
|
||||||
|
|
@ -20,78 +22,105 @@ public class InvokerFactory {
|
||||||
|
|
||||||
public static final String SUPER = Java.internalName(AbstractInvoker.class);
|
public static final String SUPER = Java.internalName(AbstractInvoker.class);
|
||||||
|
|
||||||
public static Result<AbstractInvoker> of(MetaMethod m) {
|
public static final String VALUEOF = "valueOf";
|
||||||
ClassNode classNode = ASM.createDefaultClassNode("Invoker" + m.getName() + m.getDescriptor().replaceAll("[()/;\\[]", ""), SUPER);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an invoker for a single method
|
||||||
|
*
|
||||||
|
* @param method the MetaMethod representing the method to invoke
|
||||||
|
* @return a generated instance of an AbstractInvoker
|
||||||
|
*/
|
||||||
|
public static Result<AbstractInvoker> of(MetaMethod method) {
|
||||||
|
// new ASM ClassNode with default constructor
|
||||||
|
String className = "Invoker" + method.getName() + method.getDescriptor().replaceAll("[()/;\\[]", "");
|
||||||
|
if (ByteClassLoader.INSTANCE.isDefined(className)) {
|
||||||
|
return getInvoker(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassNode classNode = ASM.createDefaultClassNode(className, SUPER);
|
||||||
|
|
||||||
|
// the invoker method
|
||||||
MethodNode invokerMethod = new MethodNode(ACC_PUBLIC,
|
MethodNode invokerMethod = new MethodNode(ACC_PUBLIC,
|
||||||
"invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
|
"invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
|
||||||
invokerMethod.instructions.add(new VarInsnNode(ALOAD, 1));
|
// local var #1 is the class to invoke the method on
|
||||||
invokerMethod.instructions.add(new TypeInsnNode(CHECKCAST, m.getMetaClass().getRawName()));
|
InsnList insns = invokerMethod.instructions;
|
||||||
for (int i = 0; i < m.getParameters().size(); i++) {
|
insns.add(new VarInsnNode(ALOAD, 1));
|
||||||
invokerMethod.instructions.add(new VarInsnNode(ALOAD, 2));
|
// it comes in as Object and has to be cast to it's actual type
|
||||||
|
insns.add(new TypeInsnNode(CHECKCAST, method.getMetaClass().getRawName()));
|
||||||
|
|
||||||
|
// the arguments are passed as varargs / array of Object
|
||||||
|
for (int i = 0; i < method.getParameters().size(); i++) {
|
||||||
|
insns.add(new VarInsnNode(ALOAD, 2));
|
||||||
if (i < 6) {
|
if (i < 6) {
|
||||||
invokerMethod.instructions.add(new InsnNode(3 + i)); //ICONST_X
|
insns.add(new InsnNode(3 + i)); //ICONST_X
|
||||||
} else {
|
} else {
|
||||||
invokerMethod.instructions.add(new LdcInsnNode(i));
|
insns.add(new LdcInsnNode(i));
|
||||||
}
|
}
|
||||||
invokerMethod.instructions.add(new InsnNode(AALOAD));
|
// put argument on the stack
|
||||||
invokerMethod.instructions.add(new TypeInsnNode(CHECKCAST, m.getParameters().get(i).getDescriptor()));
|
insns.add(new InsnNode(AALOAD));
|
||||||
|
insns.add(new TypeInsnNode(CHECKCAST, method.getParameters().get(i).getDescriptor()));
|
||||||
}
|
}
|
||||||
;
|
// call the method
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKEVIRTUAL, m.getMetaClass().getRawName(), m.getName(), m.getDescriptor()));
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, method.getMetaClass().getRawName(), method.getName(), method.getDescriptor()));
|
||||||
|
// if the returned argument is primitive, wrap it as an Object
|
||||||
|
wrapAnyPrimitivesForReturnObject(method, invokerMethod);
|
||||||
|
// return the object on the stack
|
||||||
|
insns.add(new InsnNode(ARETURN));
|
||||||
|
|
||||||
convertReturnTypeForPrimitives(m, invokerMethod);
|
// add the method to the class
|
||||||
|
|
||||||
invokerMethod.instructions.add(new InsnNode(ARETURN));
|
|
||||||
classNode.methods.add(invokerMethod);
|
classNode.methods.add(invokerMethod);
|
||||||
|
|
||||||
|
// create the bytecode
|
||||||
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||||
classNode.accept(classWriter);
|
classNode.accept(classWriter);
|
||||||
byte[] byteArray = classWriter.toByteArray();
|
byte[] byteArray = classWriter.toByteArray();
|
||||||
|
|
||||||
// try (FileOutputStream out = new FileOutputStream("C.class")) {
|
// load it into the JVM
|
||||||
// out.write(byteArray);
|
ByteClassLoader.INSTANCE.addClass(className, byteArray);
|
||||||
// } catch (IOException e) {
|
return getInvoker(classNode.name);
|
||||||
// e.printStackTrace();
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray);
|
private static Result<AbstractInvoker> getInvoker(String name) {
|
||||||
try {
|
try {
|
||||||
return ok((AbstractInvoker) ByteClassLoader.INSTANCE.loadClass(classNode.name).getConstructor().newInstance());
|
return ok((AbstractInvoker) ByteClassLoader.INSTANCE.loadClass(name).getConstructor().newInstance());
|
||||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException |
|
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException |
|
||||||
ClassNotFoundException e) {
|
ClassNotFoundException e) {
|
||||||
return err(e);
|
return err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void convertReturnTypeForPrimitives(MetaMethod m, MethodNode invokerMethod) {
|
/*
|
||||||
|
* wrap primitives in their wrapper
|
||||||
|
*/
|
||||||
|
private static void wrapAnyPrimitivesForReturnObject(MetaMethod m, MethodNode invokerMethod) {
|
||||||
|
InsnList insns = invokerMethod.instructions;
|
||||||
switch (m.getReturnParameter().getDescriptor()) {
|
switch (m.getReturnParameter().getDescriptor()) {
|
||||||
case "V":
|
case "V":
|
||||||
invokerMethod.instructions.add(new InsnNode(ACONST_NULL));
|
insns.add(new InsnNode(ACONST_NULL));
|
||||||
break;
|
break;
|
||||||
case "Z":
|
case "Z":
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, BOOLEAN, VALUEOF, "(Z)Ljava/lang/Boolean;"));
|
||||||
break;
|
break;
|
||||||
case "I":
|
case "I":
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, INTEGER, VALUEOF, "(I)Ljava/lang/Integer;"));
|
||||||
break;
|
break;
|
||||||
case "J":
|
case "J":
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, LONG, VALUEOF, "(J)Ljava/lang/Long;"));
|
||||||
break;
|
break;
|
||||||
case "F":
|
case "F":
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, FLOAT, VALUEOF, "(F)Ljava/lang/Float;"));
|
||||||
break;
|
break;
|
||||||
case "D":
|
case "D":
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, DOUBLE, VALUEOF, "(D)Ljava/lang/Double;"));
|
||||||
break;
|
break;
|
||||||
case "C":
|
case "C":
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, CHAR, VALUEOF, "(C)Ljava/lang/Character;"));
|
||||||
break;
|
break;
|
||||||
case "S":
|
case "S":
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, SHORT, VALUEOF, "(S)Ljava/lang/Short;"));
|
||||||
break;
|
break;
|
||||||
case "B":
|
case "B":
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", VALUEOF, "(B)Ljava/lang/Byte;"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,8 @@ public class ByteClassLoader extends ClassLoader {
|
||||||
Class<?> classDef = defineClass(name, bytecode, 0, bytecode.length);
|
Class<?> classDef = defineClass(name, bytecode, 0, bytecode.length);
|
||||||
classes.put(name, classDef);
|
classes.put(name, classDef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDefined(String className) {
|
||||||
|
return classes.containsKey(className);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,14 @@ public class Java {
|
||||||
public static final String ZERO_ARGS_VOID = "()V";
|
public static final String ZERO_ARGS_VOID = "()V";
|
||||||
public static final String OBJECT = "Ljava/lang/Object;";
|
public static final String OBJECT = "Ljava/lang/Object;";
|
||||||
|
|
||||||
|
public static final String BOOLEAN = "java/lang/Boolean";
|
||||||
|
public static final String INTEGER = "java/lang/Integer";
|
||||||
|
public static final String LONG = "java/lang/Long";
|
||||||
|
public static final String FLOAT = "java/lang/Float";
|
||||||
|
public static final String DOUBLE = "java/lang/Double";
|
||||||
|
public static final String CHAR = "java/lang/Character";
|
||||||
|
public static final String SHORT = "java/lang/Short";
|
||||||
|
|
||||||
public static String internalName(String className) {
|
public static String internalName(String className) {
|
||||||
return className.replaceAll("\\.", "/");
|
return className.replaceAll("\\.", "/");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,26 +25,25 @@ public class ReflectiveTest {
|
||||||
|
|
||||||
Set<MetaMethod> methods = metaDummy.getMethods();
|
Set<MetaMethod> methods = metaDummy.getMethods();
|
||||||
assertFalse(methods.isEmpty());
|
assertFalse(methods.isEmpty());
|
||||||
assertEquals(4, methods.size());
|
assertEquals(5, methods.size());
|
||||||
|
|
||||||
MetaMethod equals = metaDummy.getMethod("equals").orElseGet(Assertions::fail);
|
MetaMethod equals = metaDummy.getMethod("equals").orElseGet(Assertions::fail);
|
||||||
assertEquals(List.of(Object.class), equals.getParameters());
|
assertEquals(boolean.class, equals.getReturnParameter().getType());
|
||||||
assertEquals(boolean.class, equals.getReturnParameter());
|
|
||||||
assertTrue(Modifier.isPublic(equals.getModifiers()));
|
assertTrue(Modifier.isPublic(equals.getModifiers()));
|
||||||
|
|
||||||
MetaMethod hashCode = metaDummy.getMethod("hashCode").orElseGet(Assertions::fail);
|
MetaMethod hashCode = metaDummy.getMethod("hashCode").orElseGet(Assertions::fail);
|
||||||
assertEquals(List.of(), hashCode.getParameters());
|
assertEquals(List.of(), hashCode.getParameters());
|
||||||
assertEquals(int.class, hashCode.getReturnParameter());
|
assertEquals(int.class, hashCode.getReturnParameter().getType());
|
||||||
assertTrue(Modifier.isPublic(hashCode.getModifiers()));
|
assertTrue(Modifier.isPublic(hashCode.getModifiers()));
|
||||||
|
|
||||||
MetaMethod getName = metaDummy.getMethod("getName").orElseGet(Assertions::fail);
|
MetaMethod getName = metaDummy.getMethod("getName").orElseGet(Assertions::fail);
|
||||||
assertEquals(List.of(), getName.getParameters());
|
assertEquals(List.of(), getName.getParameters());
|
||||||
assertEquals(String.class, getName.getReturnParameter());
|
assertEquals(String.class, getName.getReturnParameter().getType());
|
||||||
assertTrue(Modifier.isPublic(getName.getModifiers()));
|
assertTrue(Modifier.isPublic(getName.getModifiers()));
|
||||||
|
|
||||||
MetaMethod privateMethod = metaDummy.getMethod("privateMethod").orElseGet(Assertions::fail);
|
MetaMethod privateMethod = metaDummy.getMethod("privateMethod").orElseGet(Assertions::fail);
|
||||||
assertEquals(List.of(), privateMethod.getParameters());
|
assertEquals(List.of(), privateMethod.getParameters());
|
||||||
assertEquals(String[].class, privateMethod.getReturnParameter());
|
assertEquals(String[].class, privateMethod.getReturnParameter().getType());
|
||||||
assertTrue(Modifier.isPrivate(privateMethod.getModifiers()));
|
assertTrue(Modifier.isPrivate(privateMethod.getModifiers()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue