From 93cd1a081aa3c12d5125ecc7230d362334ffc8f7 Mon Sep 17 00:00:00 2001 From: Sander Hautvast Date: Fri, 28 Jul 2023 18:21:35 +0200 Subject: [PATCH] invocation (not yet with arguments) --- README.md | 6 ++ .../shautvast/reflective/AbstractInvoker.java | 6 ++ .../reflective/AbstractMetaClassFactory.java | 6 -- .../shautvast/reflective/InvokerFactory.java | 54 +++++++++++ .../shautvast/reflective/MetaClass.java | 40 +++++++-- .../reflective/MetaClassFactory.java | 16 ++-- .../shautvast/reflective/MetaField.java | 12 ++- .../shautvast/reflective/MetaMethod.java | 85 +++++++++++++++++- .../shautvast/reflective/Reflective.java | 2 +- .../reflective/compare/ComparatorFactory.java | 15 +--- .../github/shautvast/reflective/java/ASM.java | 25 ++++++ .../shautvast/reflective/java/Java.java | 89 ++++++++++++++++++- .../reflective/tomap/ToMapFactory.java | 20 ++--- .../com/github/shautvast/rusty/Panic.java | 22 +++++ .../com/github/shautvast/rusty/Result.java | 68 ++++++++++++++ .../shautvast/reflective/DummyInvoker.java | 8 ++ .../shautvast/reflective/ReflectiveTest.java | 80 ++++++++++++++++- 17 files changed, 499 insertions(+), 55 deletions(-) create mode 100644 src/main/java/com/github/shautvast/reflective/AbstractInvoker.java delete mode 100644 src/main/java/com/github/shautvast/reflective/AbstractMetaClassFactory.java create mode 100644 src/main/java/com/github/shautvast/reflective/InvokerFactory.java create mode 100644 src/main/java/com/github/shautvast/reflective/java/ASM.java create mode 100644 src/main/java/com/github/shautvast/rusty/Panic.java create mode 100644 src/main/java/com/github/shautvast/rusty/Result.java create mode 100644 src/test/java/com/github/shautvast/reflective/DummyInvoker.java diff --git a/README.md b/README.md index dcff743..721c422 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,9 @@ __nl.sander.reflective.compare.Compare__ __nl.sander.reflective.tomap.ToMap__ * turn any bean/record into a Map +Now working on capabilities that mimick java.lang.reflect +* not going to create something like setAccessible(true), since that's likely impossible without jdk support, and probably not wanted either +* I do plan to substitute java.lang.reflect.Array, because of it's VERY poor performance +* a read model for methods, fields etc +* invocation capabilities + \ No newline at end of file diff --git a/src/main/java/com/github/shautvast/reflective/AbstractInvoker.java b/src/main/java/com/github/shautvast/reflective/AbstractInvoker.java new file mode 100644 index 0000000..7d4a8e6 --- /dev/null +++ b/src/main/java/com/github/shautvast/reflective/AbstractInvoker.java @@ -0,0 +1,6 @@ +package com.github.shautvast.reflective; + +public abstract class AbstractInvoker { + + public abstract Object invoke(Object instance, Object... arguments); +} diff --git a/src/main/java/com/github/shautvast/reflective/AbstractMetaClassFactory.java b/src/main/java/com/github/shautvast/reflective/AbstractMetaClassFactory.java deleted file mode 100644 index 28e582a..0000000 --- a/src/main/java/com/github/shautvast/reflective/AbstractMetaClassFactory.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.shautvast.reflective; - -public abstract class AbstractMetaClassFactory { - - public abstract MetaClass create(); -} diff --git a/src/main/java/com/github/shautvast/reflective/InvokerFactory.java b/src/main/java/com/github/shautvast/reflective/InvokerFactory.java new file mode 100644 index 0000000..1301ef9 --- /dev/null +++ b/src/main/java/com/github/shautvast/reflective/InvokerFactory.java @@ -0,0 +1,54 @@ +package com.github.shautvast.reflective; + +import com.github.shautvast.reflective.java.ASM; +import com.github.shautvast.reflective.java.ByteClassLoader; +import com.github.shautvast.reflective.java.Java; +import com.github.shautvast.rusty.Result; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.*; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.UUID; + +import static com.github.shautvast.rusty.Result.err; +import static com.github.shautvast.rusty.Result.ok; +import static org.objectweb.asm.Opcodes.*; + +public class InvokerFactory { + + public static final String SUPER = Java.internalName(AbstractInvoker.class); + + public static Result of(MetaMethod m) { + ClassNode classNode = ASM.createDefaultClassNode("Invoker" + UUID.randomUUID(), SUPER); + + MethodNode invokerMethod = new MethodNode(ACC_PUBLIC, + "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null); + invokerMethod.instructions.add(new VarInsnNode(ALOAD, 1)); + invokerMethod.instructions.add(new TypeInsnNode(CHECKCAST, m.getMetaClass().getRawName())); + invokerMethod.instructions.add(new MethodInsnNode(INVOKEVIRTUAL, m.getMetaClass().getRawName(), m.getName(), m.getDescriptor())); + invokerMethod.instructions.add(new InsnNode(ARETURN)); + classNode.methods.add(invokerMethod); + + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + classNode.accept(classWriter); + byte[] byteArray = classWriter.toByteArray(); + + try (FileOutputStream out = new FileOutputStream("C.class")) { + out.write(byteArray); + } catch (IOException e) { + e.printStackTrace(); + } + + ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray); + try { + return ok((AbstractInvoker) ByteClassLoader.INSTANCE.loadClass(classNode.name).getConstructor().newInstance()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | + ClassNotFoundException e) { + return err(e); + } + } + + +} diff --git a/src/main/java/com/github/shautvast/reflective/MetaClass.java b/src/main/java/com/github/shautvast/reflective/MetaClass.java index 5c34271..4a25aeb 100644 --- a/src/main/java/com/github/shautvast/reflective/MetaClass.java +++ b/src/main/java/com/github/shautvast/reflective/MetaClass.java @@ -1,17 +1,25 @@ package com.github.shautvast.reflective; +import com.github.shautvast.reflective.java.Java; + import java.util.*; public class MetaClass { private final String name; + private final String rawName; private final Map fields; private final Map methods; + private final Set constructors; + private final Class javaClass; - public MetaClass(String name) { - this.name = name; + public MetaClass(Class javaClass, String rawName) { + this.javaClass = javaClass; + this.name = Java.externalName(rawName); + this.rawName = rawName; this.fields = new HashMap<>(); this.methods = new HashMap<>(); + this.constructors = new HashSet<>(); } public String getName() { @@ -26,11 +34,31 @@ public class MetaClass { return Set.copyOf(methods.values()); } - void addField(int access, String name, String descriptor) { - fields.put(name, new MetaField(name)); + public Optional getMethod(String name) { + return Optional.ofNullable(methods.get(name)); } - public void addMethod(int access, String methodname, String descriptor) { - methods.put(methodname, new MetaMethod(methodname)); + public Set getConstructors() { + return constructors; + } + + void addField(int access, String name, String descriptor) { + fields.put(name, new MetaField(name, access)); //ASM access same as reflect modifiers? + } + + public void addMethod(int access, String name, String descriptor) { + methods.put(name, new MetaMethod(this, name, access, descriptor)); + } + + public void addConstructor(int access, String methodname, String descriptor) { + + } + + public Class getJavaClass() { + return javaClass; + } + + public String getRawName() { + return rawName; } } diff --git a/src/main/java/com/github/shautvast/reflective/MetaClassFactory.java b/src/main/java/com/github/shautvast/reflective/MetaClassFactory.java index 027a218..ff231b6 100644 --- a/src/main/java/com/github/shautvast/reflective/MetaClassFactory.java +++ b/src/main/java/com/github/shautvast/reflective/MetaClassFactory.java @@ -5,26 +5,24 @@ import com.github.shautvast.reflective.java.Java; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.tree.ClassNode; import static org.objectweb.asm.Opcodes.ASM9; public class MetaClassFactory extends ClassVisitor { public static final String SUPER = Java.internalName(AbstractComparator.class); - private boolean isRecord = false; - - final ClassNode classNode = new ClassNode(); + final Class javaClass; private MetaClass metaClassToBuild; - public MetaClassFactory() { + public MetaClassFactory(ClassjavaClass) { super(ASM9); + this.javaClass = javaClass; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - metaClassToBuild = new MetaClass(Java.externalName(name)); + metaClassToBuild = new MetaClass(javaClass, name); } @Override @@ -36,7 +34,11 @@ public class MetaClassFactory extends ClassVisitor { @Override public MethodVisitor visitMethod(int access, String methodname, String descriptor, String signature, String[] exceptions) { - metaClassToBuild.addMethod(access, methodname, descriptor); + if (!Java.isConstructor(methodname)) { + metaClassToBuild.addMethod(access, methodname, descriptor); + } else { + metaClassToBuild.addConstructor(access, methodname, descriptor); + } return null; } diff --git a/src/main/java/com/github/shautvast/reflective/MetaField.java b/src/main/java/com/github/shautvast/reflective/MetaField.java index 2b0140f..9a05530 100644 --- a/src/main/java/com/github/shautvast/reflective/MetaField.java +++ b/src/main/java/com/github/shautvast/reflective/MetaField.java @@ -3,8 +3,18 @@ package com.github.shautvast.reflective; public class MetaField { private final String name; + private final int modifiers; - public MetaField(String name) { + public MetaField(String name, int modifiers) { this.name = name; + this.modifiers = modifiers; + } + + public String getName() { + return name; + } + + public int getModifiers() { + return modifiers; } } diff --git a/src/main/java/com/github/shautvast/reflective/MetaMethod.java b/src/main/java/com/github/shautvast/reflective/MetaMethod.java index ea9a240..f217a4f 100644 --- a/src/main/java/com/github/shautvast/reflective/MetaMethod.java +++ b/src/main/java/com/github/shautvast/reflective/MetaMethod.java @@ -1,9 +1,88 @@ package com.github.shautvast.reflective; -public class MetaMethod { - private final String name; +import com.github.shautvast.reflective.java.Java; +import com.github.shautvast.rusty.Result; - public MetaMethod(String methodname) { +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import static com.github.shautvast.rusty.Result.err; +import static com.github.shautvast.rusty.Result.ok; + +public class MetaMethod { + private final MetaClass metaClass; + private final String name; + private final int modifiers; + private final String descriptor; + private List> parameters = new LinkedList<>(); + private Class returnParameter; + private final AbstractInvoker invoker = InvokerFactory.of(this).unwrapOr(() -> null); + + public MetaMethod(MetaClass metaClass, String methodname, int modifiers, String descriptor) { + this.metaClass = metaClass; this.name = methodname; + this.modifiers = modifiers; + this.descriptor = descriptor; + getParameters(descriptor); + } + + public String getName() { + return name; + } + + public MetaClass getMetaClass() { + return metaClass; + } + + public int getModifiers() { + return modifiers; + } + + public String getDescriptor() { + return descriptor; + } + + public List> getParameters() { + return parameters; + } + + public Class getReturnParameter() { + return returnParameter; + } + + @SuppressWarnings("unchecked") + public Result invoke(Object instance, Object... arguments) { + if (instance.getClass() != metaClass.getJavaClass()) { + return Result.err("instance type not of " + metaClass.getJavaClass()); + } + + try { + T invoke = (T) invoker.invoke(instance, arguments); + return ok(invoke); + } catch (Exception e) { + return err(e); + } + } + + private void getParameters(String descriptor) { + String[] split = descriptor.split("[()]"); + String parms = split[1]; + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < parms.length(); i++) { + String t = Character.toString(parms.charAt(i)); + if (!"L".equals(t) && buf.length() == 0) { + this.parameters.add(Java.getClassFromDescriptor(t)); + } + if (";".equals(t)) { + buf.append(t); + this.parameters.add(Java.getClassFromDescriptor(buf.toString())); + buf = new StringBuilder(); + } else { + buf.append(t); + } + } + this.parameters = Collections.unmodifiableList(this.parameters); //effectively final + this.returnParameter = Java.getClassFromDescriptor(split[2]); } } diff --git a/src/main/java/com/github/shautvast/reflective/Reflective.java b/src/main/java/com/github/shautvast/reflective/Reflective.java index 87ecfe4..4a72248 100644 --- a/src/main/java/com/github/shautvast/reflective/Reflective.java +++ b/src/main/java/com/github/shautvast/reflective/Reflective.java @@ -13,7 +13,7 @@ public class Reflective { try { ClassReader cr = Java.getClassReader(type); - MetaClassFactory factory = new MetaClassFactory(); + MetaClassFactory factory = new MetaClassFactory(type); cr.accept(factory, ClassReader.SKIP_FRAMES); return factory.getMetaClass(); } catch (Exception e) { diff --git a/src/main/java/com/github/shautvast/reflective/compare/ComparatorFactory.java b/src/main/java/com/github/shautvast/reflective/compare/ComparatorFactory.java index 78dab52..f7db67c 100644 --- a/src/main/java/com/github/shautvast/reflective/compare/ComparatorFactory.java +++ b/src/main/java/com/github/shautvast/reflective/compare/ComparatorFactory.java @@ -1,5 +1,6 @@ package com.github.shautvast.reflective.compare; +import com.github.shautvast.reflective.java.ASM; import com.github.shautvast.reflective.java.Java; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; @@ -20,10 +21,8 @@ class ComparatorFactory extends ClassVisitor { super(ASM9); } - final ClassNode classNode = new ClassNode(); - private String classToMap; - + ClassNode classNode; private MethodNode compareMethod; private int localVarIndex = 0; @@ -34,15 +33,7 @@ class ComparatorFactory extends ClassVisitor { isRecord = true; } this.classToMap = name; - classNode.name = "Apple" + UUID.randomUUID(); - classNode.superName = SUPER; - classNode.version = V11; - classNode.access = ACC_PUBLIC; - MethodNode constructor = new MethodNode(ACC_PUBLIC, Java.INIT, Java.ZERO_ARGS_VOID, null, null); - constructor.instructions.add(new VarInsnNode(ALOAD, 0)); - constructor.instructions.add(new MethodInsnNode(INVOKESPECIAL, SUPER, Java.INIT, Java.ZERO_ARGS_VOID)); - constructor.instructions.add(new InsnNode(RETURN)); - classNode.methods.add(constructor); + classNode = ASM.createDefaultClassNode("Apple" + UUID.randomUUID(), SUPER); compareMethod = new MethodNode(ACC_PUBLIC, "compare", "(Ljava/lang/Object;Ljava/lang/Object;)L" + Java.internalName(Result.class) + ";", null, null); diff --git a/src/main/java/com/github/shautvast/reflective/java/ASM.java b/src/main/java/com/github/shautvast/reflective/java/ASM.java new file mode 100644 index 0000000..47a938f --- /dev/null +++ b/src/main/java/com/github/shautvast/reflective/java/ASM.java @@ -0,0 +1,25 @@ +package com.github.shautvast.reflective.java; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +import static org.objectweb.asm.Opcodes.*; + +public class ASM { + private ASM() { + } + + public static ClassNode createDefaultClassNode(String name, String superClass) { + ClassNode classNode = new ClassNode(Opcodes.ASM9); + classNode.name = name; + classNode.superName = superClass; + classNode.version = V11; + classNode.access = ACC_PUBLIC; + MethodNode constructor = new MethodNode(ACC_PUBLIC, Java.INIT, Java.ZERO_ARGS_VOID, null, null); + constructor.instructions.add(new VarInsnNode(ALOAD, 0)); + constructor.instructions.add(new MethodInsnNode(INVOKESPECIAL, superClass, Java.INIT, Java.ZERO_ARGS_VOID)); + constructor.instructions.add(new InsnNode(RETURN)); + classNode.methods.add(constructor); + return classNode; + } +} 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 eb11b02..8338c8e 100644 --- a/src/main/java/com/github/shautvast/reflective/java/Java.java +++ b/src/main/java/com/github/shautvast/reflective/java/Java.java @@ -4,6 +4,8 @@ import org.objectweb.asm.ClassReader; import java.io.IOException; +import static java.lang.reflect.Array.newInstance; + /* * common utils not for external use */ @@ -21,8 +23,8 @@ public class Java { return internalName(type.getName()); } - public static String externalName(String internalName){ - return internalName.replaceAll("/","."); + public static String externalName(String internalName) { + return internalName.replaceAll("/", "."); } public static boolean hasArgs(String desc) { @@ -56,4 +58,87 @@ public class Java { throw new ClassNotFoundException(type.getName()); } } + + public static boolean isConstructor(String methodname) { + return INIT.equals(methodname); + } + + public static Class getTypeFrom(String typedesc) { + switch (typedesc) { + case "B": + return byte.class; + case "I": + return int.class; + case "J": + return long.class; + case "Z": + return boolean.class; + case "C": + return char.class; + case "S": + return short.class; + case "F": + return float.class; + case "D": + return double.class; + + default: + try { + if (typedesc.startsWith("L")) { + return Class.forName(externalName(typedesc.substring(1))); + } + if (typedesc.startsWith("[") && !typedesc.endsWith(";")) { + return Class.forName(externalName(typedesc.substring(1))); + } + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + throw new RuntimeException(); + } + + public static Class getClassFromDescriptor(String descriptor) { + int arrayDims = 0; + while (descriptor.startsWith("[")) { + arrayDims++; + descriptor = descriptor.substring(1); + } //could be cheaper + + if (descriptor.startsWith("L") && descriptor.endsWith(";")) { + try { + String className = descriptor.substring(1, descriptor.length() - 1).replaceAll("/", "."); + Class clazz = Class.forName(className); + if (arrayDims > 0) { + clazz = newInstance(clazz, new int[arrayDims]).getClass(); + } + return clazz; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); // not supposed to happen + } + } else { + char typeChar = descriptor.charAt(0); + boolean isArray = arrayDims != 0; + switch (typeChar) { + case 'B': + return isArray ? newInstance(byte.class, new int[arrayDims]).getClass() : byte.class; + case 'C': + return isArray ? newInstance(char.class, new int[arrayDims]).getClass() : char.class; + case 'D': + return isArray ? newInstance(double.class, new int[arrayDims]).getClass() : double.class; + case 'F': + return isArray ? newInstance(float.class, new int[arrayDims]).getClass() : float.class; + case 'I': + return isArray ? newInstance(int.class, new int[arrayDims]).getClass() : int.class; + case 'J': + return isArray ? newInstance(long.class, new int[arrayDims]).getClass() : long.class; + case 'S': + return isArray ? newInstance(short.class, new int[arrayDims]).getClass() : short.class; + case 'Z': + return isArray ? newInstance(boolean.class, new int[arrayDims]).getClass() : boolean.class; + default: + throw new RuntimeException(new ClassNotFoundException("unknown descriptor: " + descriptor)); //must not happen + } + } + } } diff --git a/src/main/java/com/github/shautvast/reflective/tomap/ToMapFactory.java b/src/main/java/com/github/shautvast/reflective/tomap/ToMapFactory.java index 4bd07f6..a73040d 100644 --- a/src/main/java/com/github/shautvast/reflective/tomap/ToMapFactory.java +++ b/src/main/java/com/github/shautvast/reflective/tomap/ToMapFactory.java @@ -1,5 +1,6 @@ package com.github.shautvast.reflective.tomap; +import com.github.shautvast.reflective.java.ASM; import com.github.shautvast.reflective.java.Java; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; @@ -13,10 +14,9 @@ import static org.objectweb.asm.Opcodes.*; class ToMapFactory extends ClassVisitor { public static final String SUPER_NAME = Java.internalName(AbstractToMap.class.getName()); private boolean isRecord = false; - final ClassNode classNode = new ClassNode(); private String classToMap; - + ClassNode classNode; private MethodNode mappifyMethod; @@ -30,15 +30,7 @@ class ToMapFactory extends ClassVisitor { isRecord = true; } this.classToMap = name; - classNode.name = "ToMap" + UUID.randomUUID(); - classNode.superName = SUPER_NAME; - classNode.version = V11; - classNode.access = ACC_PUBLIC; - MethodNode constructor = new MethodNode(ACC_PUBLIC, Java.INIT, Java.ZERO_ARGS_VOID, null, null); - constructor.instructions.add(new VarInsnNode(ALOAD, 0)); - constructor.instructions.add(new MethodInsnNode(INVOKESPECIAL, SUPER_NAME, Java.INIT, Java.ZERO_ARGS_VOID)); - constructor.instructions.add(new InsnNode(RETURN)); - classNode.methods.add(constructor); + classNode = ASM.createDefaultClassNode("ToMap" + UUID.randomUUID(), SUPER_NAME); mappifyMethod = new MethodNode(ACC_PUBLIC, "toMap", "(Ljava/lang/Object;)Ljava/util/Map;", null, null); @@ -66,11 +58,11 @@ class ToMapFactory extends ClassVisitor { add(new VarInsnNode(ALOAD, 1)); add(new TypeInsnNode(CHECKCAST, Java.internalName(classToMap))); add(new MethodInsnNode(INVOKEVIRTUAL, classToMap, getterMethodName, "()" + returnType)); - add(new MethodInsnNode(INVOKEVIRTUAL, classNode.name, "add", "(Ljava/util/HashMap;Ljava/lang/String;"+translate(returnType)+")V")); + add(new MethodInsnNode(INVOKEVIRTUAL, classNode.name, "add", "(Ljava/util/HashMap;Ljava/lang/String;" + translate(returnType) + ")V")); } - private String translate(String typeDesc){ - if (typeDesc.startsWith("L")){ + private String translate(String typeDesc) { + if (typeDesc.startsWith("L")) { return Java.OBJECT; } else { return typeDesc; diff --git a/src/main/java/com/github/shautvast/rusty/Panic.java b/src/main/java/com/github/shautvast/rusty/Panic.java new file mode 100644 index 0000000..56fcbf4 --- /dev/null +++ b/src/main/java/com/github/shautvast/rusty/Panic.java @@ -0,0 +1,22 @@ +package com.github.shautvast.rusty; + +public class Panic extends RuntimeException{ + public Panic() { + } + + public Panic(String message) { + super(message); + } + + public Panic(String message, Throwable cause) { + super(message, cause); + } + + public Panic(Throwable cause) { + super(cause); + } + + public Panic(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/github/shautvast/rusty/Result.java b/src/main/java/com/github/shautvast/rusty/Result.java new file mode 100644 index 0000000..35a6569 --- /dev/null +++ b/src/main/java/com/github/shautvast/rusty/Result.java @@ -0,0 +1,68 @@ +package com.github.shautvast.rusty; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * attempted rust-style exception handling + * + * @param type of expected result + */ +public class Result { + private final T value; + private final Panic error; + + private Result(T value, Throwable error) { + this.value = value; + this.error = new Panic(error); + } + + private Result(T value) { + this.value = value; + this.error = null; + } + + public static Result err(Throwable error) { + return new Result<>(null, new Panic(error)); + } + + public static Result err(String error) { + return new Result<>(null, new Panic(error)); + } + + public static Result ok(T value) { + return new Result<>(value); + } + + public T unwrap() { + if (error == null) { + return value; + } else { + throw error; + } + } + + public T expect(String failedExpectation) { + if (error == null) { + return value; + } else { + throw new Panic(failedExpectation); + } + } + + public T unwrapOr(Supplier valueSupplier) { + if (error == null) { + return value; + } else { + return valueSupplier.get(); + } + } + + public Result map(Function mappingFunction) { + if (error == null) { + return new Result<>(mappingFunction.apply(value), null); + } else { + return new Result<>(null, error); + } + } +} diff --git a/src/test/java/com/github/shautvast/reflective/DummyInvoker.java b/src/test/java/com/github/shautvast/reflective/DummyInvoker.java new file mode 100644 index 0000000..fa13fe6 --- /dev/null +++ b/src/test/java/com/github/shautvast/reflective/DummyInvoker.java @@ -0,0 +1,8 @@ +package com.github.shautvast.reflective; + +public class DummyInvoker extends AbstractInvoker { + + public Object invoke(Object instance, Object... arguments) { + return ((ReflectiveTest.Dummy) instance).getName(); + } +} diff --git a/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java b/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java index 9bf6cc9..ebf7ac3 100644 --- a/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java +++ b/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java @@ -1,13 +1,87 @@ package com.github.shautvast.reflective; +import com.github.shautvast.rusty.Panic; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import java.lang.reflect.Modifier; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; public class ReflectiveTest { @Test - void test(){ - assertEquals("java.lang.String",Reflective.getMetaForClass(String.class).getName()); + void testMethods() { + Dummy dummy = new Dummy("bar"); + MetaClass metaDummy = Reflective.getMetaForClass(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(4, methods.size()); + + MetaMethod equals = metaDummy.getMethod("equals").orElseGet(Assertions::fail); + assertEquals(List.of(Object.class), equals.getParameters()); + assertEquals(boolean.class, equals.getReturnParameter()); + assertTrue(Modifier.isPublic(equals.getModifiers())); + + MetaMethod hashCode = metaDummy.getMethod("hashCode").orElseGet(Assertions::fail); + assertEquals(List.of(), hashCode.getParameters()); + assertEquals(int.class, hashCode.getReturnParameter()); + assertTrue(Modifier.isPublic(hashCode.getModifiers())); + + MetaMethod getName = metaDummy.getMethod("getName").orElseGet(Assertions::fail); + assertEquals(List.of(), getName.getParameters()); + assertEquals(String.class, getName.getReturnParameter()); + assertTrue(Modifier.isPublic(getName.getModifiers())); + + MetaMethod privateMethod = metaDummy.getMethod("privateMethod").orElseGet(Assertions::fail); + assertEquals(List.of(), privateMethod.getParameters()); + assertEquals(String[].class, privateMethod.getReturnParameter()); + assertTrue(Modifier.isPrivate(privateMethod.getModifiers())); + } + + + @Test + void testInvocation() { + Dummy dummy = new Dummy("bar"); + MetaMethod getName = Reflective.getMetaForClass(dummy.getClass()).getMethod("getName").orElseGet(Assertions::fail); + + assertThrows(Panic.class, () -> getName.invoke("foo").unwrap()); + assertEquals("bar", getName.invoke(dummy).unwrap()); + } + + + public static class Dummy { + private final String name; + + public Dummy(String name) { + this.name = name; + } + + public String getName() { + return privateMethod()[0]; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Dummy; + } + + @Override + public int hashCode() { + return 6; + } + + private String[] privateMethod() { + return new String[]{name, "bar"}; + } } }