fixed bug with primitive arguments
This commit is contained in:
parent
1a09f3e687
commit
7de5019a4f
6 changed files with 141 additions and 52 deletions
8
pom.xml
8
pom.xml
|
|
@ -4,7 +4,7 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.github.shautvast</groupId>
|
<groupId>com.github.shautvast</groupId>
|
||||||
<artifactId>reflective</artifactId>
|
<artifactId>reflective</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.1.0</version>
|
||||||
<name>reflective</name>
|
<name>reflective</name>
|
||||||
<description>Reflective utils that don't use java.lang.reflect</description>
|
<description>Reflective utils that don't use java.lang.reflect</description>
|
||||||
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<artifactId>jol-core</artifactId>
|
<artifactId>jol-core</artifactId>
|
||||||
<version>0.17</version>
|
<version>0.17</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.26</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<distributionManagement>
|
<distributionManagement>
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
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.DOUBLE;
|
||||||
import static com.github.shautvast.reflective.java.Java.FLOAT;
|
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.INTEGER;
|
||||||
import static com.github.shautvast.reflective.java.Java.LONG;
|
import static com.github.shautvast.reflective.java.Java.LONG;
|
||||||
|
import static com.github.shautvast.reflective.java.Java.*;
|
||||||
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.*;
|
||||||
|
|
@ -58,12 +58,13 @@ public class InvokerFactory {
|
||||||
}
|
}
|
||||||
// put argument on the stack
|
// put argument on the stack
|
||||||
insns.add(new InsnNode(AALOAD));
|
insns.add(new InsnNode(AALOAD));
|
||||||
insns.add(new TypeInsnNode(CHECKCAST, method.getParameters().get(i).getDescriptor()));
|
insns.add(new TypeInsnNode(CHECKCAST, Java.internalName(mapPrimitiveToWrapper(method.getParameters().get(i).getType()).getName())));
|
||||||
|
unwrapPrimitive(method.getParameters().get(i), insns);
|
||||||
}
|
}
|
||||||
// call the method
|
// call the method
|
||||||
insns.add(new MethodInsnNode(INVOKEVIRTUAL, method.getMetaClass().getRawName(), method.getName(), method.getDescriptor()));
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, method.getMetaClass().getRawName(), method.getName(), method.getDescriptor()));
|
||||||
// if the returned argument is primitive, wrap it as an Object
|
// if the returned argument is primitive, wrap it as an Object
|
||||||
wrapAnyPrimitivesForReturnObject(method, invokerMethod);
|
wrapPrimitive(method.getReturnParameter(), invokerMethod.instructions);
|
||||||
// return the object on the stack
|
// return the object on the stack
|
||||||
insns.add(new InsnNode(ARETURN));
|
insns.add(new InsnNode(ARETURN));
|
||||||
|
|
||||||
|
|
@ -75,7 +76,7 @@ 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"+method.getName() + ".class")) {
|
||||||
// out.write(byteArray);
|
// out.write(byteArray);
|
||||||
// } catch (IOException e) {
|
// } catch (IOException e) {
|
||||||
// e.printStackTrace();
|
// e.printStackTrace();
|
||||||
|
|
@ -95,12 +96,39 @@ public class InvokerFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void unwrapPrimitive(Parameter<?> parameter, InsnList insns) {
|
||||||
|
switch (parameter.getDescriptor()) {
|
||||||
|
case "B":
|
||||||
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B"));
|
||||||
|
break;
|
||||||
|
case "S":
|
||||||
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S"));
|
||||||
|
break;
|
||||||
|
case "I":
|
||||||
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I"));
|
||||||
|
break;
|
||||||
|
case "Z":
|
||||||
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"));
|
||||||
|
break;
|
||||||
|
case "J":
|
||||||
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J"));
|
||||||
|
break;
|
||||||
|
case "F":
|
||||||
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F"));
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D"));
|
||||||
|
break;
|
||||||
|
case "C":
|
||||||
|
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* wrap primitives in their wrapper
|
* wrap primitives in their wrapper
|
||||||
*/
|
*/
|
||||||
private static void wrapAnyPrimitivesForReturnObject(MetaMethod m, MethodNode invokerMethod) {
|
private static void wrapPrimitive(Parameter<?> parameter, InsnList insns) {
|
||||||
InsnList insns = invokerMethod.instructions;
|
switch (parameter.getDescriptor()) {
|
||||||
switch (m.getReturnParameter().getDescriptor()) {
|
|
||||||
case "V":
|
case "V":
|
||||||
insns.add(new InsnNode(ACONST_NULL));
|
insns.add(new InsnNode(ACONST_NULL));
|
||||||
break;
|
break;
|
||||||
|
|
@ -126,10 +154,32 @@ public class InvokerFactory {
|
||||||
insns.add(new MethodInsnNode(INVOKESTATIC, SHORT, VALUEOF, "(S)Ljava/lang/Short;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, SHORT, VALUEOF, "(S)Ljava/lang/Short;"));
|
||||||
break;
|
break;
|
||||||
case "B":
|
case "B":
|
||||||
insns.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", VALUEOF, "(B)Ljava/lang/Byte;"));
|
insns.add(new MethodInsnNode(INVOKESTATIC, BYTE, VALUEOF, "(B)Ljava/lang/Byte;"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Class<?> mapPrimitiveToWrapper(Class<?> type) {
|
||||||
|
if (type == int.class) {
|
||||||
|
return Integer.class;
|
||||||
|
} else if (type == byte.class) {
|
||||||
|
return Byte.class;
|
||||||
|
} else if (type == short.class) {
|
||||||
|
return Short.class;
|
||||||
|
} else if (type == long.class) {
|
||||||
|
return Long.class;
|
||||||
|
} else if (type == float.class) {
|
||||||
|
return Float.class;
|
||||||
|
} else if (type == double.class) {
|
||||||
|
return Double.class;
|
||||||
|
} else if (type == char.class) {
|
||||||
|
return Character.class;
|
||||||
|
} else if (type == boolean.class) {
|
||||||
|
return Boolean.class;
|
||||||
|
} else {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public class MetaClass {
|
||||||
return Set.copyOf(methods.values());
|
return Set.copyOf(methods.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// should account for overloading!
|
||||||
public Optional<MetaMethod> getMethod(String name) {
|
public Optional<MetaMethod> getMethod(String name) {
|
||||||
return Optional.ofNullable(methods.get(name));
|
return Optional.ofNullable(methods.get(name));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ public class Java {
|
||||||
public static final String DOUBLE = "java/lang/Double";
|
public static final String DOUBLE = "java/lang/Double";
|
||||||
public static final String CHAR = "java/lang/Character";
|
public static final String CHAR = "java/lang/Character";
|
||||||
public static final String SHORT = "java/lang/Short";
|
public static final String SHORT = "java/lang/Short";
|
||||||
|
public static final String BYTE = "java/lang/Byte";
|
||||||
|
|
||||||
public static String internalName(String className) {
|
public static String internalName(String className) {
|
||||||
return className.replaceAll("\\.", "/");
|
return className.replaceAll("\\.", "/");
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package com.github.shautvast.reflective;
|
package com.github.shautvast.reflective;
|
||||||
|
|
||||||
|
@SuppressWarnings("ALL")
|
||||||
public class DummyInvoker extends AbstractInvoker {
|
public class DummyInvoker extends AbstractInvoker {
|
||||||
|
|
||||||
public Object invoke(Object instance, Object... arguments) {
|
public Object invoke(Object instance, Object... arguments) {
|
||||||
((ReflectiveTest.Dummy) instance).setName((String)arguments[0]);
|
((ReflectiveTest.Dummy) instance).setIntValue((int)arguments[0]);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ package com.github.shautvast.reflective;
|
||||||
|
|
||||||
import com.github.shautvast.rusty.Panic;
|
import com.github.shautvast.rusty.Panic;
|
||||||
import com.github.shautvast.rusty.Result;
|
import com.github.shautvast.rusty.Result;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
@ -16,17 +18,13 @@ public class ReflectiveTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMethods() {
|
void testMethods() {
|
||||||
Dummy dummy = new Dummy("bar");
|
Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!");
|
||||||
MetaClass metaDummy = Reflective.getMetaClass(dummy.getClass());
|
MetaClass metaDummy = Reflective.getMetaClass(dummy.getClass());
|
||||||
assertEquals("com.github.shautvast.reflective.ReflectiveTest$Dummy", metaDummy.getName());
|
assertEquals("com.github.shautvast.reflective.ReflectiveTest$Dummy", metaDummy.getName());
|
||||||
|
|
||||||
Iterator<MetaField> fields = metaDummy.getFields().iterator();
|
|
||||||
assertTrue(fields.hasNext());
|
|
||||||
assertEquals("name", fields.next().getName());
|
|
||||||
|
|
||||||
Set<MetaMethod> methods = metaDummy.getMethods();
|
Set<MetaMethod> methods = metaDummy.getMethods();
|
||||||
assertFalse(methods.isEmpty());
|
assertFalse(methods.isEmpty());
|
||||||
assertEquals(6, methods.size());
|
assertEquals(22, methods.size());
|
||||||
|
|
||||||
MetaMethod equals = metaDummy.getMethod("equals").orElseGet(Assertions::fail);
|
MetaMethod equals = metaDummy.getMethod("equals").orElseGet(Assertions::fail);
|
||||||
assertEquals(boolean.class, equals.getReturnParameter().getType());
|
assertEquals(boolean.class, equals.getReturnParameter().getType());
|
||||||
|
|
@ -37,10 +35,10 @@ public class ReflectiveTest {
|
||||||
assertEquals(int.class, hashCode.getReturnParameter().getType());
|
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 getStringValue = metaDummy.getMethod("getStringValue").orElseGet(Assertions::fail);
|
||||||
assertEquals(List.of(), getName.getParameters());
|
assertEquals(List.of(), getStringValue.getParameters());
|
||||||
assertEquals(String.class, getName.getReturnParameter().getType());
|
assertEquals(String.class, getStringValue.getReturnParameter().getType());
|
||||||
assertTrue(Modifier.isPublic(getName.getModifiers()));
|
assertTrue(Modifier.isPublic(getStringValue.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());
|
||||||
|
|
@ -51,29 +49,60 @@ public class ReflectiveTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testInvokeGetter() {
|
void testInvokeGetter() {
|
||||||
Dummy dummy = new Dummy("bar");
|
Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!");
|
||||||
MetaMethod getName = Reflective.getMetaClass(dummy.getClass()).getMethod("getName").orElseGet(Assertions::fail);
|
MetaMethod getStringValue = Reflective.getMetaClass(dummy.getClass()).getMethod("getStringValue").orElseGet(Assertions::fail);
|
||||||
|
|
||||||
// passing "foo" as the instance is not allowed
|
// passing "foo" as the instance is not allowed
|
||||||
assertThrows(Panic.class, () -> getName.invoke("foo").unwrap());
|
assertThrows(Panic.class, () -> getStringValue.invoke("foo").unwrap());
|
||||||
// we should pass a valid dummy instance
|
// we should pass a valid dummy instance
|
||||||
assertEquals("bar", getName.invoke(dummy).unwrap());
|
assertEquals("don't panic!", getStringValue.invoke(dummy).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testInvokeSetter() {
|
void testInvokeSetters() {
|
||||||
Dummy dummy = new Dummy("bar");
|
Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!");
|
||||||
MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass());
|
MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass());
|
||||||
MetaMethod setName = metaForClass.getMethod("setName").orElseGet(Assertions::fail);
|
|
||||||
|
|
||||||
assertEquals("bar", dummy.getName()); // before invoke
|
MetaMethod setByte = metaForClass.getMethod("setByteValue").orElseGet(Assertions::fail);
|
||||||
setName.invoke(dummy, "foo");
|
setByte.invoke(dummy, (byte) -42).unwrap();
|
||||||
assertEquals("foo", dummy.getName()); // after invoke
|
assertEquals((byte) -42, dummy.getByteValue());
|
||||||
|
|
||||||
|
MetaMethod setShort = metaForClass.getMethod("setShortValue").orElseGet(Assertions::fail);
|
||||||
|
setShort.invoke(dummy, (short) -43).unwrap();
|
||||||
|
assertEquals((short) -43, dummy.getShortValue());
|
||||||
|
|
||||||
|
MetaMethod setInt = metaForClass.getMethod("setIntValue").orElseGet(Assertions::fail);
|
||||||
|
setInt.invoke(dummy, -44).unwrap();
|
||||||
|
assertEquals(-44, dummy.getIntValue());
|
||||||
|
|
||||||
|
MetaMethod setLongValue = metaForClass.getMethod("setLongValue").orElseGet(Assertions::fail);
|
||||||
|
setLongValue.invoke(dummy, -45L).unwrap();
|
||||||
|
assertEquals(-45L, dummy.getLongValue());
|
||||||
|
|
||||||
|
MetaMethod setFloat = metaForClass.getMethod("setFloatValue").orElseGet(Assertions::fail);
|
||||||
|
setFloat.invoke(dummy, -46.0F).unwrap();
|
||||||
|
assertEquals(-46.0F, dummy.getFloatValue());
|
||||||
|
|
||||||
|
MetaMethod setDouble = metaForClass.getMethod("setDoubleValue").orElseGet(Assertions::fail);
|
||||||
|
setDouble.invoke(dummy, -47.0).unwrap();
|
||||||
|
assertEquals(-47.0, dummy.getDoubleValue());
|
||||||
|
|
||||||
|
MetaMethod setChar = metaForClass.getMethod("setCharValue").orElseGet(Assertions::fail);
|
||||||
|
setChar.invoke(dummy, '-').unwrap();
|
||||||
|
assertEquals('-', dummy.getCharValue());
|
||||||
|
|
||||||
|
MetaMethod setBoolean = metaForClass.getMethod("setBooleanValue").orElseGet(Assertions::fail);
|
||||||
|
setBoolean.invoke(dummy, false).unwrap();
|
||||||
|
assertFalse(dummy.isBooleanValue());
|
||||||
|
|
||||||
|
MetaMethod setString = metaForClass.getMethod("setStringValue").orElseGet(Assertions::fail);
|
||||||
|
setString.invoke(dummy, "panic!").unwrap();
|
||||||
|
assertEquals("panic!", dummy.getStringValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testInvocationExceptionHappened() {
|
void testInvocationExceptionHappened() {
|
||||||
Dummy dummy = new Dummy("bar");
|
Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!");
|
||||||
MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass());
|
MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass());
|
||||||
MetaMethod throwEx = metaForClass.getMethod("throwEx").orElseGet(Assertions::fail);
|
MetaMethod throwEx = metaForClass.getMethod("throwEx").orElseGet(Assertions::fail);
|
||||||
|
|
||||||
|
|
@ -81,25 +110,19 @@ public class ReflectiveTest {
|
||||||
assertFalse(result.isOk());
|
assertFalse(result.isOk());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
public static class Dummy {
|
public static class Dummy {
|
||||||
private String name;
|
private byte byteValue;
|
||||||
|
private short shortValue;
|
||||||
public Dummy(String name) {
|
private int intValue;
|
||||||
this.name = name;
|
private long longValue;
|
||||||
}
|
private float floatValue;
|
||||||
|
private double doubleValue;
|
||||||
public String getName() {
|
private char charValue;
|
||||||
return privateMethod()[0];
|
private boolean booleanValue;
|
||||||
}
|
private String stringValue;
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void throwEx() throws Exception {
|
|
||||||
throw new Exception("something must have gone wrong");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
|
|
@ -111,8 +134,15 @@ public class ReflectiveTest {
|
||||||
return 6;
|
return 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private String[] privateMethod(){
|
private String[] privateMethod(){
|
||||||
return new String[]{name, "bar"};
|
return new String[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public void throwEx() {
|
||||||
|
throw new RuntimeException("ex");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue