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>
|
||||
<groupId>com.github.shautvast</groupId>
|
||||
<artifactId>reflective</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
<name>reflective</name>
|
||||
<description>Reflective utils that don't use java.lang.reflect</description>
|
||||
|
||||
|
|
@ -29,6 +29,12 @@
|
|||
<artifactId>jol-core</artifactId>
|
||||
<version>0.17</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.26</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<distributionManagement>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ import org.objectweb.asm.tree.*;
|
|||
|
||||
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.FLOAT;
|
||||
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.*;
|
||||
import static com.github.shautvast.rusty.Result.err;
|
||||
import static com.github.shautvast.rusty.Result.ok;
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
|
@ -58,12 +58,13 @@ public class InvokerFactory {
|
|||
}
|
||||
// put argument on the stack
|
||||
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
|
||||
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);
|
||||
wrapPrimitive(method.getReturnParameter(), invokerMethod.instructions);
|
||||
// return the object on the stack
|
||||
insns.add(new InsnNode(ARETURN));
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ public class InvokerFactory {
|
|||
classNode.accept(classWriter);
|
||||
byte[] byteArray = classWriter.toByteArray();
|
||||
|
||||
// try (FileOutputStream out = new FileOutputStream("C.class")) {
|
||||
// try (FileOutputStream out = new FileOutputStream("C"+method.getName() + ".class")) {
|
||||
// out.write(byteArray);
|
||||
// } catch (IOException e) {
|
||||
// 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
|
||||
*/
|
||||
private static void wrapAnyPrimitivesForReturnObject(MetaMethod m, MethodNode invokerMethod) {
|
||||
InsnList insns = invokerMethod.instructions;
|
||||
switch (m.getReturnParameter().getDescriptor()) {
|
||||
private static void wrapPrimitive(Parameter<?> parameter, InsnList insns) {
|
||||
switch (parameter.getDescriptor()) {
|
||||
case "V":
|
||||
insns.add(new InsnNode(ACONST_NULL));
|
||||
break;
|
||||
|
|
@ -126,10 +154,32 @@ public class InvokerFactory {
|
|||
insns.add(new MethodInsnNode(INVOKESTATIC, SHORT, VALUEOF, "(S)Ljava/lang/Short;"));
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
// should account for overloading!
|
||||
public Optional<MetaMethod> getMethod(String 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 CHAR = "java/lang/Character";
|
||||
public static final String SHORT = "java/lang/Short";
|
||||
public static final String BYTE = "java/lang/Byte";
|
||||
|
||||
public static String internalName(String className) {
|
||||
return className.replaceAll("\\.", "/");
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package com.github.shautvast.reflective;
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
public class DummyInvoker extends AbstractInvoker {
|
||||
|
||||
public Object invoke(Object instance, Object... arguments) {
|
||||
((ReflectiveTest.Dummy) instance).setName((String)arguments[0]);
|
||||
((ReflectiveTest.Dummy) instance).setIntValue((int)arguments[0]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package com.github.shautvast.reflective;
|
|||
|
||||
import com.github.shautvast.rusty.Panic;
|
||||
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.Test;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
|
@ -16,17 +18,13 @@ public class ReflectiveTest {
|
|||
|
||||
@Test
|
||||
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());
|
||||
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();
|
||||
assertFalse(methods.isEmpty());
|
||||
assertEquals(6, methods.size());
|
||||
assertEquals(22, methods.size());
|
||||
|
||||
MetaMethod equals = metaDummy.getMethod("equals").orElseGet(Assertions::fail);
|
||||
assertEquals(boolean.class, equals.getReturnParameter().getType());
|
||||
|
|
@ -37,10 +35,10 @@ public class ReflectiveTest {
|
|||
assertEquals(int.class, hashCode.getReturnParameter().getType());
|
||||
assertTrue(Modifier.isPublic(hashCode.getModifiers()));
|
||||
|
||||
MetaMethod getName = metaDummy.getMethod("getName").orElseGet(Assertions::fail);
|
||||
assertEquals(List.of(), getName.getParameters());
|
||||
assertEquals(String.class, getName.getReturnParameter().getType());
|
||||
assertTrue(Modifier.isPublic(getName.getModifiers()));
|
||||
MetaMethod getStringValue = metaDummy.getMethod("getStringValue").orElseGet(Assertions::fail);
|
||||
assertEquals(List.of(), getStringValue.getParameters());
|
||||
assertEquals(String.class, getStringValue.getReturnParameter().getType());
|
||||
assertTrue(Modifier.isPublic(getStringValue.getModifiers()));
|
||||
|
||||
MetaMethod privateMethod = metaDummy.getMethod("privateMethod").orElseGet(Assertions::fail);
|
||||
assertEquals(List.of(), privateMethod.getParameters());
|
||||
|
|
@ -51,29 +49,60 @@ public class ReflectiveTest {
|
|||
|
||||
@Test
|
||||
void testInvokeGetter() {
|
||||
Dummy dummy = new Dummy("bar");
|
||||
MetaMethod getName = Reflective.getMetaClass(dummy.getClass()).getMethod("getName").orElseGet(Assertions::fail);
|
||||
Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!");
|
||||
MetaMethod getStringValue = Reflective.getMetaClass(dummy.getClass()).getMethod("getStringValue").orElseGet(Assertions::fail);
|
||||
|
||||
// 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
|
||||
assertEquals("bar", getName.invoke(dummy).unwrap());
|
||||
assertEquals("don't panic!", getStringValue.invoke(dummy).unwrap());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvokeSetter() {
|
||||
Dummy dummy = new Dummy("bar");
|
||||
void testInvokeSetters() {
|
||||
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());
|
||||
MetaMethod setName = metaForClass.getMethod("setName").orElseGet(Assertions::fail);
|
||||
|
||||
assertEquals("bar", dummy.getName()); // before invoke
|
||||
setName.invoke(dummy, "foo");
|
||||
assertEquals("foo", dummy.getName()); // after invoke
|
||||
MetaMethod setByte = metaForClass.getMethod("setByteValue").orElseGet(Assertions::fail);
|
||||
setByte.invoke(dummy, (byte) -42).unwrap();
|
||||
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
|
||||
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());
|
||||
MetaMethod throwEx = metaForClass.getMethod("throwEx").orElseGet(Assertions::fail);
|
||||
|
||||
|
|
@ -81,25 +110,19 @@ public class ReflectiveTest {
|
|||
assertFalse(result.isOk());
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public static class Dummy {
|
||||
private String name;
|
||||
|
||||
public Dummy(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return privateMethod()[0];
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void throwEx() throws Exception {
|
||||
throw new Exception("something must have gone wrong");
|
||||
}
|
||||
private byte byteValue;
|
||||
private short shortValue;
|
||||
private int intValue;
|
||||
private long longValue;
|
||||
private float floatValue;
|
||||
private double doubleValue;
|
||||
private char charValue;
|
||||
private boolean booleanValue;
|
||||
private String stringValue;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
|
@ -111,8 +134,15 @@ public class ReflectiveTest {
|
|||
return 6;
|
||||
}
|
||||
|
||||
private String[] privateMethod() {
|
||||
return new String[]{name, "bar"};
|
||||
@SuppressWarnings("unused")
|
||||
private String[] privateMethod(){
|
||||
return new String[1];
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void throwEx() {
|
||||
throw new RuntimeException("ex");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue