added support for method arguments (maybe incomplete, should add tests with arrays)
This commit is contained in:
parent
93cd1a081a
commit
89d20eec57
6 changed files with 133 additions and 21 deletions
|
|
@ -21,13 +21,27 @@ 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 Result<AbstractInvoker> of(MetaMethod m) {
|
||||||
ClassNode classNode = ASM.createDefaultClassNode("Invoker" + UUID.randomUUID(), SUPER);
|
ClassNode classNode = ASM.createDefaultClassNode("Invoker" + m.getName() + m.getDescriptor().replaceAll("[()/;\\[]", ""), SUPER);
|
||||||
|
|
||||||
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));
|
invokerMethod.instructions.add(new VarInsnNode(ALOAD, 1));
|
||||||
invokerMethod.instructions.add(new TypeInsnNode(CHECKCAST, m.getMetaClass().getRawName()));
|
invokerMethod.instructions.add(new TypeInsnNode(CHECKCAST, m.getMetaClass().getRawName()));
|
||||||
|
for (int i = 0; i < m.getParameters().size(); i++) {
|
||||||
|
invokerMethod.instructions.add(new VarInsnNode(ALOAD, 2));
|
||||||
|
if (i < 6) {
|
||||||
|
invokerMethod.instructions.add(new InsnNode(3 + i)); //ICONST_X
|
||||||
|
} else {
|
||||||
|
invokerMethod.instructions.add(new LdcInsnNode(i));
|
||||||
|
}
|
||||||
|
invokerMethod.instructions.add(new InsnNode(AALOAD));
|
||||||
|
invokerMethod.instructions.add(new TypeInsnNode(CHECKCAST, m.getParameters().get(i).getDescriptor()));
|
||||||
|
}
|
||||||
|
;
|
||||||
invokerMethod.instructions.add(new MethodInsnNode(INVOKEVIRTUAL, m.getMetaClass().getRawName(), m.getName(), m.getDescriptor()));
|
invokerMethod.instructions.add(new MethodInsnNode(INVOKEVIRTUAL, m.getMetaClass().getRawName(), m.getName(), m.getDescriptor()));
|
||||||
|
|
||||||
|
convertReturnTypeForPrimitives(m, invokerMethod);
|
||||||
|
|
||||||
invokerMethod.instructions.add(new InsnNode(ARETURN));
|
invokerMethod.instructions.add(new InsnNode(ARETURN));
|
||||||
classNode.methods.add(invokerMethod);
|
classNode.methods.add(invokerMethod);
|
||||||
|
|
||||||
|
|
@ -35,11 +49,11 @@ public class InvokerFactory {
|
||||||
classNode.accept(classWriter);
|
classNode.accept(classWriter);
|
||||||
byte[] byteArray = classWriter.toByteArray();
|
byte[] byteArray = classWriter.toByteArray();
|
||||||
|
|
||||||
try (FileOutputStream out = new FileOutputStream("C.class")) {
|
// try (FileOutputStream out = new FileOutputStream("C.class")) {
|
||||||
out.write(byteArray);
|
// out.write(byteArray);
|
||||||
} catch (IOException e) {
|
// } catch (IOException e) {
|
||||||
e.printStackTrace();
|
// e.printStackTrace();
|
||||||
}
|
// }
|
||||||
|
|
||||||
ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray);
|
ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray);
|
||||||
try {
|
try {
|
||||||
|
|
@ -50,5 +64,37 @@ public class InvokerFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void convertReturnTypeForPrimitives(MetaMethod m, MethodNode invokerMethod) {
|
||||||
|
switch (m.getReturnParameter().getDescriptor()) {
|
||||||
|
case "V":
|
||||||
|
invokerMethod.instructions.add(new InsnNode(ACONST_NULL));
|
||||||
|
break;
|
||||||
|
case "Z":
|
||||||
|
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"));
|
||||||
|
break;
|
||||||
|
case "I":
|
||||||
|
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"));
|
||||||
|
break;
|
||||||
|
case "J":
|
||||||
|
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"));
|
||||||
|
break;
|
||||||
|
case "F":
|
||||||
|
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"));
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"));
|
||||||
|
break;
|
||||||
|
case "C":
|
||||||
|
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"));
|
||||||
|
break;
|
||||||
|
case "S":
|
||||||
|
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"));
|
||||||
|
break;
|
||||||
|
case "B":
|
||||||
|
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ package com.github.shautvast.reflective;
|
||||||
import com.github.shautvast.reflective.java.Java;
|
import com.github.shautvast.reflective.java.Java;
|
||||||
import com.github.shautvast.rusty.Result;
|
import com.github.shautvast.rusty.Result;
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.github.shautvast.rusty.Result.err;
|
import static com.github.shautvast.rusty.Result.err;
|
||||||
|
|
@ -15,16 +16,21 @@ public class MetaMethod {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final int modifiers;
|
private final int modifiers;
|
||||||
private final String descriptor;
|
private final String descriptor;
|
||||||
private List<Class<?>> parameters = new LinkedList<>();
|
private final List<Parameter<?>> parameters;
|
||||||
private Class<?> returnParameter;
|
private Parameter<?> returnParameter;
|
||||||
private final AbstractInvoker invoker = InvokerFactory.of(this).unwrapOr(() -> null);
|
private final AbstractInvoker invoker;
|
||||||
|
|
||||||
public MetaMethod(MetaClass metaClass, String methodname, int modifiers, String descriptor) {
|
public MetaMethod(MetaClass metaClass, String methodname, int modifiers, String descriptor) {
|
||||||
this.metaClass = metaClass;
|
this.metaClass = metaClass;
|
||||||
this.name = methodname;
|
this.name = methodname;
|
||||||
this.modifiers = modifiers;
|
this.modifiers = modifiers;
|
||||||
this.descriptor = descriptor;
|
this.descriptor = descriptor;
|
||||||
getParameters(descriptor);
|
this.parameters = createParameters(descriptor);
|
||||||
|
if (!Modifier.isPrivate(modifiers)) {
|
||||||
|
invoker = InvokerFactory.of(this).unwrap();//TODO
|
||||||
|
} else {
|
||||||
|
invoker = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|
@ -43,11 +49,11 @@ public class MetaMethod {
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Class<?>> getParameters() {
|
public List<Parameter<?>> getParameters() {
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Class<?> getReturnParameter() {
|
public Parameter<?> getReturnParameter() {
|
||||||
return returnParameter;
|
return returnParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,24 +71,39 @@ public class MetaMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getParameters(String descriptor) {
|
private List<Parameter<?>> createParameters(String descriptor) {
|
||||||
|
List<Parameter<?>> mutableParams = new ArrayList<>();
|
||||||
String[] split = descriptor.split("[()]");
|
String[] split = descriptor.split("[()]");
|
||||||
String parms = split[1];
|
String parms = split[1];
|
||||||
StringBuilder buf = new StringBuilder();
|
StringBuilder buf = new StringBuilder();
|
||||||
for (int i = 0; i < parms.length(); i++) {
|
for (int i = 0; i < parms.length(); i++) {
|
||||||
String t = Character.toString(parms.charAt(i));
|
String t = Character.toString(parms.charAt(i));
|
||||||
if (!"L".equals(t) && buf.length() == 0) {
|
if (!"L".equals(t) && buf.length() == 0) {
|
||||||
this.parameters.add(Java.getClassFromDescriptor(t));
|
mutableParams.add(new Parameter<>(Java.getClassFromDescriptor(t), t));
|
||||||
}
|
}
|
||||||
if (";".equals(t)) {
|
if (";".equals(t)) {
|
||||||
buf.append(t);
|
buf.append(t);
|
||||||
this.parameters.add(Java.getClassFromDescriptor(buf.toString()));
|
String desc = buf.toString();
|
||||||
|
mutableParams.add(new Parameter<>(Java.getClassFromDescriptor(desc), correct(desc)));
|
||||||
buf = new StringBuilder();
|
buf = new StringBuilder();
|
||||||
} else {
|
} else {
|
||||||
buf.append(t);
|
buf.append(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.parameters = Collections.unmodifiableList(this.parameters); //effectively final
|
|
||||||
this.returnParameter = Java.getClassFromDescriptor(split[2]);
|
String returnDesc = split[2];
|
||||||
|
this.returnParameter = new Parameter<>(
|
||||||
|
Java.getClassFromDescriptor(returnDesc),
|
||||||
|
correct(returnDesc));
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(mutableParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String correct(String returnDesc) {
|
||||||
|
if (returnDesc.startsWith("L")) {
|
||||||
|
return returnDesc.substring(1, returnDesc.length() - 1);
|
||||||
|
} else {
|
||||||
|
return returnDesc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
src/main/java/com/github/shautvast/reflective/Parameter.java
Normal file
27
src/main/java/com/github/shautvast/reflective/Parameter.java
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.github.shautvast.reflective;
|
||||||
|
|
||||||
|
public class Parameter<T> {
|
||||||
|
private final Class<T> type;
|
||||||
|
private final String descriptor;
|
||||||
|
|
||||||
|
public Parameter(Class<T> type, String descriptor) {
|
||||||
|
this.type = type;
|
||||||
|
this.descriptor = descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<T> getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVoid(){
|
||||||
|
return type == Void.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrimitive(){
|
||||||
|
return !descriptor.startsWith("L");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptor() {
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -136,6 +136,8 @@ public class Java {
|
||||||
return isArray ? newInstance(short.class, new int[arrayDims]).getClass() : short.class;
|
return isArray ? newInstance(short.class, new int[arrayDims]).getClass() : short.class;
|
||||||
case 'Z':
|
case 'Z':
|
||||||
return isArray ? newInstance(boolean.class, new int[arrayDims]).getClass() : boolean.class;
|
return isArray ? newInstance(boolean.class, new int[arrayDims]).getClass() : boolean.class;
|
||||||
|
case 'V':
|
||||||
|
return Void.class;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException(new ClassNotFoundException("unknown descriptor: " + descriptor)); //must not happen
|
throw new RuntimeException(new ClassNotFoundException("unknown descriptor: " + descriptor)); //must not happen
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.github.shautvast.reflective;
|
||||||
public class DummyInvoker extends AbstractInvoker {
|
public class DummyInvoker extends AbstractInvoker {
|
||||||
|
|
||||||
public Object invoke(Object instance, Object... arguments) {
|
public Object invoke(Object instance, Object... arguments) {
|
||||||
return ((ReflectiveTest.Dummy) instance).getName();
|
((ReflectiveTest.Dummy) instance).setName((String)arguments[0]);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ public class ReflectiveTest {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testInvocation() {
|
void testInvokeGetter() {
|
||||||
Dummy dummy = new Dummy("bar");
|
Dummy dummy = new Dummy("bar");
|
||||||
MetaMethod getName = Reflective.getMetaForClass(dummy.getClass()).getMethod("getName").orElseGet(Assertions::fail);
|
MetaMethod getName = Reflective.getMetaForClass(dummy.getClass()).getMethod("getName").orElseGet(Assertions::fail);
|
||||||
|
|
||||||
|
|
@ -58,9 +58,20 @@ public class ReflectiveTest {
|
||||||
assertEquals("bar", getName.invoke(dummy).unwrap());
|
assertEquals("bar", getName.invoke(dummy).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvokeSetter() {
|
||||||
|
Dummy dummy = new Dummy("bar");
|
||||||
|
MetaClass metaForClass = Reflective.getMetaForClass(dummy.getClass());
|
||||||
|
MetaMethod setName = metaForClass.getMethod("setName").orElseGet(Assertions::fail);
|
||||||
|
|
||||||
|
assertEquals("bar", dummy.getName()); // before invoke
|
||||||
|
setName.invoke(dummy, "foo");
|
||||||
|
assertEquals("foo", dummy.getName()); // after invoke
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class Dummy {
|
public static class Dummy {
|
||||||
private final String name;
|
private String name;
|
||||||
|
|
||||||
public Dummy(String name) {
|
public Dummy(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
@ -70,6 +81,10 @@ public class ReflectiveTest {
|
||||||
return privateMethod()[0];
|
return privateMethod()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
return obj instanceof Dummy;
|
return obj instanceof Dummy;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue