invocation (not yet with arguments)
This commit is contained in:
parent
7bc4b96fd7
commit
93cd1a081a
17 changed files with 499 additions and 55 deletions
|
|
@ -12,3 +12,9 @@ __nl.sander.reflective.compare.Compare__
|
|||
__nl.sander.reflective.tomap.ToMap__
|
||||
* turn any bean/record into a Map<String, Object>
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.github.shautvast.reflective;
|
||||
|
||||
public abstract class AbstractInvoker {
|
||||
|
||||
public abstract Object invoke(Object instance, Object... arguments);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.github.shautvast.reflective;
|
||||
|
||||
public abstract class AbstractMetaClassFactory {
|
||||
|
||||
public abstract MetaClass create();
|
||||
}
|
||||
|
|
@ -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<AbstractInvoker> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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<String, MetaField> fields;
|
||||
private final Map<String, MetaMethod> methods;
|
||||
private final Set<MetaMethod> 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<MetaMethod> 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<MetaMethod> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(Class<?>javaClass) {
|
||||
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) {
|
||||
if (!Java.isConstructor(methodname)) {
|
||||
metaClassToBuild.addMethod(access, methodname, descriptor);
|
||||
} else {
|
||||
metaClassToBuild.addConstructor(access, methodname, descriptor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Class<?>> 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<Class<?>> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public Class<?> getReturnParameter() {
|
||||
return returnParameter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Result<T> 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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
25
src/main/java/com/github/shautvast/reflective/java/ASM.java
Normal file
25
src/main/java/com/github/shautvast/reflective/java/ASM.java
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
22
src/main/java/com/github/shautvast/rusty/Panic.java
Normal file
22
src/main/java/com/github/shautvast/rusty/Panic.java
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
68
src/main/java/com/github/shautvast/rusty/Result.java
Normal file
68
src/main/java/com/github/shautvast/rusty/Result.java
Normal file
|
|
@ -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 <T> type of expected result
|
||||
*/
|
||||
public class Result<T> {
|
||||
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 <T> Result<T> err(Throwable error) {
|
||||
return new Result<>(null, new Panic(error));
|
||||
}
|
||||
|
||||
public static <T> Result<T> err(String error) {
|
||||
return new Result<>(null, new Panic(error));
|
||||
}
|
||||
|
||||
public static <T> Result<T> 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<T> valueSupplier) {
|
||||
if (error == null) {
|
||||
return value;
|
||||
} else {
|
||||
return valueSupplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
public <R> Result<R> map(Function<T, R> mappingFunction) {
|
||||
if (error == null) {
|
||||
return new Result<>(mappingFunction.apply(value), null);
|
||||
} else {
|
||||
return new Result<>(null, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MetaField> fields = metaDummy.getFields().iterator();
|
||||
assertTrue(fields.hasNext());
|
||||
assertEquals("name", fields.next().getName());
|
||||
|
||||
Set<MetaMethod> 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"};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue