diff --git a/pom.xml b/pom.xml index eea9ba3..2f4d736 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.github.shautvast reflective - 1.0.0 + 1.1.0 reflective Reflective utils that don't use java.lang.reflect @@ -29,6 +29,12 @@ jol-core 0.17 + + org.projectlombok + lombok + 1.18.26 + test + diff --git a/src/main/java/com/github/shautvast/reflective/InvokerFactory.java b/src/main/java/com/github/shautvast/reflective/InvokerFactory.java index bb08a32..c120c9f 100644 --- a/src/main/java/com/github/shautvast/reflective/InvokerFactory.java +++ b/src/main/java/com/github/shautvast/reflective/InvokerFactory.java @@ -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; + } + } + } diff --git a/src/main/java/com/github/shautvast/reflective/MetaClass.java b/src/main/java/com/github/shautvast/reflective/MetaClass.java index 4a25aeb..0dfd467 100644 --- a/src/main/java/com/github/shautvast/reflective/MetaClass.java +++ b/src/main/java/com/github/shautvast/reflective/MetaClass.java @@ -34,6 +34,7 @@ public class MetaClass { return Set.copyOf(methods.values()); } + // should account for overloading! public Optional getMethod(String name) { return Optional.ofNullable(methods.get(name)); } diff --git a/src/main/java/com/github/shautvast/reflective/java/Java.java b/src/main/java/com/github/shautvast/reflective/java/Java.java index 16c9047..d440b41 100644 --- a/src/main/java/com/github/shautvast/reflective/java/Java.java +++ b/src/main/java/com/github/shautvast/reflective/java/Java.java @@ -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("\\.", "/"); diff --git a/src/test/java/com/github/shautvast/reflective/DummyInvoker.java b/src/test/java/com/github/shautvast/reflective/DummyInvoker.java index fcaa43e..072d33d 100644 --- a/src/test/java/com/github/shautvast/reflective/DummyInvoker.java +++ b/src/test/java/com/github/shautvast/reflective/DummyInvoker.java @@ -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; } } diff --git a/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java b/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java index 0291d99..5a32924 100644 --- a/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java +++ b/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java @@ -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 fields = metaDummy.getFields().iterator(); - assertTrue(fields.hasNext()); - assertEquals("name", fields.next().getName()); - Set 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"); + } + } }