Compare commits
10 commits
3e636667a9
...
fbfd8d5e93
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbfd8d5e93 | ||
|
|
021304a5d8 | ||
|
|
207d91cd6c | ||
|
|
31f2b7c0f5 | ||
|
|
7ccf137898 | ||
|
|
86aff15ee5 | ||
|
|
7de5019a4f | ||
|
|
1a09f3e687 | ||
|
|
58c2afcce0 | ||
|
|
bba00f1cbd |
26 changed files with 997 additions and 135 deletions
13
pom.xml
13
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.2.1</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>
|
||||||
|
|
||||||
|
|
@ -24,6 +24,17 @@
|
||||||
<artifactId>asm-tree</artifactId>
|
<artifactId>asm-tree</artifactId>
|
||||||
<version>9.4</version>
|
<version>9.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjdk.jol</groupId>
|
||||||
|
<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>
|
</dependencies>
|
||||||
|
|
||||||
<distributionManagement>
|
<distributionManagement>
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,15 @@ import com.github.shautvast.rusty.Result;
|
||||||
import org.objectweb.asm.ClassWriter;
|
import org.objectweb.asm.ClassWriter;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
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.*;
|
||||||
|
|
@ -32,7 +34,7 @@ public class InvokerFactory {
|
||||||
*/
|
*/
|
||||||
public static Result<AbstractInvoker> of(MetaMethod method) {
|
public static Result<AbstractInvoker> of(MetaMethod method) {
|
||||||
// new ASM ClassNode with default constructor
|
// new ASM ClassNode with default constructor
|
||||||
String className = "Invoker" + method.getName() + method.getDescriptor().replaceAll("[()/;\\[]", "");
|
String className = "Invoker" + method.getMetaClass().getJavaClass().getSimpleName() + method.getName() + method.getDescriptor().replaceAll("[()/;\\[]", "");
|
||||||
if (ByteClassLoader.INSTANCE.isDefined(className)) {
|
if (ByteClassLoader.INSTANCE.isDefined(className)) {
|
||||||
return getInvoker(className);
|
return getInvoker(className);
|
||||||
}
|
}
|
||||||
|
|
@ -58,12 +60,14 @@ 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()));
|
String type = internalName(mapPrimitiveToWrapper(method.getParameters().get(i).getType()).getName());
|
||||||
|
insns.add(new TypeInsnNode(CHECKCAST, type));
|
||||||
|
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,6 +79,12 @@ public class InvokerFactory {
|
||||||
classNode.accept(classWriter);
|
classNode.accept(classWriter);
|
||||||
byte[] byteArray = classWriter.toByteArray();
|
byte[] byteArray = classWriter.toByteArray();
|
||||||
|
|
||||||
|
try (FileOutputStream out = new FileOutputStream(method.getMetaClass().getName() + "_" + method.getName() + ".class")) {
|
||||||
|
out.write(byteArray);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
// load it into the JVM
|
// load it into the JVM
|
||||||
ByteClassLoader.INSTANCE.addClass(className, byteArray);
|
ByteClassLoader.INSTANCE.addClass(className, byteArray);
|
||||||
return getInvoker(classNode.name);
|
return getInvoker(classNode.name);
|
||||||
|
|
@ -89,12 +99,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;
|
||||||
|
|
@ -120,10 +157,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package com.github.shautvast.reflective;
|
|
||||||
|
|
||||||
import com.github.shautvast.reflective.MetaClass;
|
|
||||||
import com.github.shautvast.reflective.Reflective;
|
|
||||||
|
|
||||||
public class Main {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
Dummy dummy = new Dummy();
|
|
||||||
dummy.setName("foo");
|
|
||||||
MetaClass metaDummy = Reflective.getMetaClass(dummy.getClass());
|
|
||||||
|
|
||||||
metaDummy.getMethod("setName")
|
|
||||||
.ifPresent(m -> m.invoke(dummy, "bar"));
|
|
||||||
System.out.println(dummy.getName()); // prints "bar"
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Dummy {
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +44,7 @@ public class MetaClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
void addField(int access, String name, String descriptor) {
|
void addField(int access, String name, String descriptor) {
|
||||||
fields.put(name, new MetaField(name, access)); //ASM access same as reflect modifiers?
|
fields.put(name, new MetaField(name, access, Java.toClass(descriptor))); //ASM access same as reflect modifiers?
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMethod(int access, String name, String descriptor) {
|
public void addMethod(int access, String name, String descriptor) {
|
||||||
|
|
@ -61,4 +62,6 @@ public class MetaClass {
|
||||||
public String getRawName() {
|
public String getRawName() {
|
||||||
return rawName;
|
return rawName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,12 @@ public class MetaField {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final int modifiers;
|
private final int modifiers;
|
||||||
|
|
||||||
public MetaField(String name, int modifiers) {
|
private final Class<?> type;
|
||||||
|
|
||||||
|
public MetaField(String name, int modifiers, Class<?> type) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.modifiers = modifiers;
|
this.modifiers = modifiers;
|
||||||
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|
|
||||||
|
|
@ -78,13 +78,13 @@ public class MetaMethod {
|
||||||
StringBuilder buf = new StringBuilder();
|
StringBuilder buf = new StringBuilder();
|
||||||
for (int i = 0; i < parms.length(); i++) {
|
for (int i = 0; i < parms.length(); i++) {
|
||||||
String t = Character.toString(parms.charAt(i));
|
String t = Character.toString(parms.charAt(i));
|
||||||
if (!"L".equals(t) && buf.length() == 0) {
|
if (!"[".equals(t) && !"L".equals(t) && buf.length() == 0) {
|
||||||
mutableParams.add(new Parameter<>(Java.getClassFromDescriptor(t), t));
|
mutableParams.add(new Parameter<>(Java.toClass(t), t));
|
||||||
}
|
}
|
||||||
if (";".equals(t)) {
|
if (";".equals(t)) {
|
||||||
buf.append(t);
|
buf.append(t);
|
||||||
String desc = buf.toString();
|
String desc = buf.toString();
|
||||||
mutableParams.add(new Parameter<>(Java.getClassFromDescriptor(desc), correct(desc)));
|
mutableParams.add(new Parameter<>(Java.toClass(desc), correct(desc)));
|
||||||
buf = new StringBuilder();
|
buf = new StringBuilder();
|
||||||
} else {
|
} else {
|
||||||
buf.append(t);
|
buf.append(t);
|
||||||
|
|
@ -93,7 +93,7 @@ public class MetaMethod {
|
||||||
|
|
||||||
String returnDesc = split[2];
|
String returnDesc = split[2];
|
||||||
this.returnParameter = new Parameter<>(
|
this.returnParameter = new Parameter<>(
|
||||||
Java.getClassFromDescriptor(returnDesc),
|
Java.toClass(returnDesc),
|
||||||
correct(returnDesc));
|
correct(returnDesc));
|
||||||
|
|
||||||
return Collections.unmodifiableList(mutableParams);
|
return Collections.unmodifiableList(mutableParams);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ public class Parameter<T> {
|
||||||
private final String descriptor;
|
private final String descriptor;
|
||||||
|
|
||||||
public Parameter(Class<T> type, String descriptor) {
|
public Parameter(Class<T> type, String descriptor) {
|
||||||
|
if (type ==null){
|
||||||
|
throw new IllegalArgumentException("Type cannot be null");
|
||||||
|
}
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.descriptor = descriptor;
|
this.descriptor = descriptor;
|
||||||
}
|
}
|
||||||
|
|
@ -17,10 +20,6 @@ public class Parameter<T> {
|
||||||
return type == Void.class;
|
return type == Void.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPrimitive(){
|
|
||||||
return !descriptor.startsWith("L");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescriptor() {
|
public String getDescriptor() {
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
package com.github.shautvast.reflective.array;
|
||||||
|
|
||||||
|
import com.github.shautvast.reflective.array.base.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public interface for dynamically working with arrays (create, set and get operations)
|
||||||
|
* <p>
|
||||||
|
* Drop in replacement for java.lang.reflect.Array
|
||||||
|
*/
|
||||||
|
public final class ArrayFactory {
|
||||||
|
|
||||||
|
private ArrayFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new array of the specified type and dimensions
|
||||||
|
*
|
||||||
|
* @param elementType the array element type
|
||||||
|
* @param dimensions array of ints, ie {10} means 1 dimension, length 10. {10,20} means 2 dimensions, size 10 by 20
|
||||||
|
* @return an object that you can cast to the expected array type
|
||||||
|
*/
|
||||||
|
public static Object newInstance(Class<?> elementType, int... dimensions) {
|
||||||
|
return ArrayHandlerFactory.getCreatorInstance(elementType, dimensions).newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an Object value on an array
|
||||||
|
*
|
||||||
|
* @param array the array on which the value is set
|
||||||
|
* @param index the array index
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
public static void set(Object array, int index, Object value) {
|
||||||
|
ArrayHandlerFactory.getAccessorInstance(ObjectArrayAccessor.class, typeChecked(array)).set(array, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a Object value from an array
|
||||||
|
*
|
||||||
|
* @param array the array to access
|
||||||
|
* @param index
|
||||||
|
* @return the value of the array at the index
|
||||||
|
*/
|
||||||
|
public static Object get(Object array, int index) {
|
||||||
|
return ArrayHandlerFactory.getAccessorInstance(ObjectArrayAccessor.class, typeChecked(array)).get(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an int value on an array
|
||||||
|
*
|
||||||
|
* @param array the array on which the value is set
|
||||||
|
* @param index the array index
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
public static void setInt(Object array, int index, int value) {
|
||||||
|
ArrayHandlerFactory.getAccessorInstance(IntArrayAccessor.class, typeChecked(array)).set(array, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an int value from an array
|
||||||
|
*
|
||||||
|
* @param array the array to access
|
||||||
|
* @param index
|
||||||
|
* @return the value of the array at the index
|
||||||
|
*/
|
||||||
|
public static int getInt(Object array, int index) {
|
||||||
|
return ArrayHandlerFactory.getAccessorInstance(IntArrayAccessor.class, typeChecked(array)).get(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a byte value on an array
|
||||||
|
*
|
||||||
|
* @param array the array on which the value is set
|
||||||
|
* @param index the array index
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
public static void setByte(Object array, int index, byte value) {
|
||||||
|
ArrayHandlerFactory.getAccessorInstance(ByteArrayAccessor.class, typeChecked(array)).set(array, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a byte value from an array
|
||||||
|
*
|
||||||
|
* @param array the array to access
|
||||||
|
* @param index
|
||||||
|
* @return the value of the array at the index
|
||||||
|
*/
|
||||||
|
public static byte getByte(Object array, int index) {
|
||||||
|
return ArrayHandlerFactory.getAccessorInstance(ByteArrayAccessor.class, typeChecked(array)).get(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a short value on an array
|
||||||
|
*
|
||||||
|
* @param array the array on which the value is set
|
||||||
|
* @param index the array index
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
public static void setShort(Object array, int index, short value) {
|
||||||
|
ArrayHandlerFactory.getAccessorInstance(ShortArrayAccessor.class, typeChecked(array)).set(array, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a short value from an array
|
||||||
|
*
|
||||||
|
* @param array the array to access
|
||||||
|
* @param index
|
||||||
|
* @return the value of the array at the index
|
||||||
|
*/
|
||||||
|
public static short getShort(Object array, int index) {
|
||||||
|
return ArrayHandlerFactory.getAccessorInstance(ShortArrayAccessor.class, typeChecked(array)).get(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a long value on an array
|
||||||
|
*
|
||||||
|
* @param array the array on which the value is set
|
||||||
|
* @param index the array index
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
public static void setLong(Object array, int index, long value) {
|
||||||
|
ArrayHandlerFactory.getAccessorInstance(LongArrayAccessor.class, typeChecked(array)).set(array, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a long value from an array
|
||||||
|
*
|
||||||
|
* @param array the array to access
|
||||||
|
* @param index
|
||||||
|
* @return the value of the array at the index
|
||||||
|
*/
|
||||||
|
public static long getLong(Object array, int index) {
|
||||||
|
return ArrayHandlerFactory.getAccessorInstance(LongArrayAccessor.class, typeChecked(array)).get(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a byte value on an array
|
||||||
|
*
|
||||||
|
* @param array the array on which the value is set
|
||||||
|
* @param index the array index
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
public static void setFloat(Object array, int index, float value) {
|
||||||
|
ArrayHandlerFactory.getAccessorInstance(FloatArrayAccessor.class, typeChecked(array)).set(array, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a float value from an array
|
||||||
|
*
|
||||||
|
* @param array the array to access
|
||||||
|
* @param index
|
||||||
|
* @return the value of the array at the index
|
||||||
|
*/
|
||||||
|
public static float getFloat(Object array, int index) {
|
||||||
|
return ArrayHandlerFactory.getAccessorInstance(FloatArrayAccessor.class, typeChecked(array)).get(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a double value on an array
|
||||||
|
*
|
||||||
|
* @param array the array on which the value is set
|
||||||
|
* @param index the array index
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
public static void setDouble(Object array, int index, double value) {
|
||||||
|
ArrayHandlerFactory.getAccessorInstance(DoubleArrayAccessor.class, typeChecked(array)).set(array, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a double value from an array
|
||||||
|
*
|
||||||
|
* @param array the array to access
|
||||||
|
* @param index
|
||||||
|
* @return the value of the array at the index
|
||||||
|
*/
|
||||||
|
public static double getDouble(Object array, int index) {
|
||||||
|
return ArrayHandlerFactory.getAccessorInstance(DoubleArrayAccessor.class, typeChecked(array)).get(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a char value on an array
|
||||||
|
*
|
||||||
|
* @param array the array on which the value is set
|
||||||
|
* @param index the array index
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
public static void setChar(Object array, int index, char value) {
|
||||||
|
ArrayHandlerFactory.getAccessorInstance(CharArrayAccessor.class, typeChecked(array)).set(array, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a char value from an array
|
||||||
|
*
|
||||||
|
* @param array the array to access
|
||||||
|
* @param index
|
||||||
|
* @return the value of the array at the index
|
||||||
|
*/
|
||||||
|
public static char getChar(Object array, int index) {
|
||||||
|
return ArrayHandlerFactory.getAccessorInstance(CharArrayAccessor.class, typeChecked(array)).get(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a boolean value on an array
|
||||||
|
*
|
||||||
|
* @param array the array on which the value is set
|
||||||
|
* @param index the array index
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
public static void setBoolean(Object array, int index, boolean value) {
|
||||||
|
ArrayHandlerFactory.getAccessorInstance(BooleanArrayAccessor.class, typeChecked(array)).set(array, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a boolean value from an array
|
||||||
|
*
|
||||||
|
* @param array the array to access
|
||||||
|
* @param index
|
||||||
|
* @return the value of the array at the index
|
||||||
|
*/
|
||||||
|
public static boolean getBoolean(Object array, int index) {
|
||||||
|
return ArrayHandlerFactory.getAccessorInstance(BooleanArrayAccessor.class, typeChecked(array)).get(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only checks if object is an array, not the type of that array. TODO
|
||||||
|
*/
|
||||||
|
private static Class<?> typeChecked(Object array) {
|
||||||
|
if (array == null) {
|
||||||
|
throw new NullPointerException("Argument is null");
|
||||||
|
}
|
||||||
|
Class<?> arrayType = array.getClass();
|
||||||
|
if (!arrayType.isArray()) {
|
||||||
|
throw new IllegalArgumentException("Argument is not an array");
|
||||||
|
}
|
||||||
|
return arrayType;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
package com.github.shautvast.reflective.array;
|
||||||
|
|
||||||
|
import com.github.shautvast.reflective.array.base.ArrayCreator;
|
||||||
|
import com.github.shautvast.reflective.array.base.ArrayAccessor;
|
||||||
|
import com.github.shautvast.reflective.array.base.ObjectArrayAccessor;
|
||||||
|
import com.github.shautvast.reflective.java.ASM;
|
||||||
|
import com.github.shautvast.reflective.java.ByteClassLoader;
|
||||||
|
import com.github.shautvast.reflective.java.Java;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static org.objectweb.asm.Opcodes.*;
|
||||||
|
|
||||||
|
class ArrayHandlerFactory {
|
||||||
|
/* cache for the compiled creator classes */
|
||||||
|
private static final Map<String, ArrayCreator> creatorCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/* Cache for the compiled accessor classes.
|
||||||
|
* The outer Map contains the ArrayAccessor type (some primitive or Object Accessor)
|
||||||
|
* That maps to the concrete calculated ArrayAccessor instance name which maps to the instance itself.
|
||||||
|
* TODO see if this can be optimized
|
||||||
|
*/
|
||||||
|
private static final Map<Class<? extends ArrayAccessor>, Map<String, Object>> accessorCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* generic method for creating array accessors (primitives and objects)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static <T extends ArrayAccessor> T getAccessorInstance(Class<T> accessorBaseType, Class<?> arrayType) {
|
||||||
|
String arrayTypeName = Java.internalName(arrayType);
|
||||||
|
String syntheticClassName = "com/shautvast/reflective/array/ArrayAccessor_"
|
||||||
|
+ Java.javaName(arrayTypeName) + Java.getNumDimensions(arrayType);
|
||||||
|
|
||||||
|
return (T) accessorCache.computeIfAbsent(accessorBaseType, k -> new ConcurrentHashMap<>()).
|
||||||
|
computeIfAbsent(syntheticClassName,
|
||||||
|
k -> ArrayHandlerFactory.createSyntheticArrayAccessor(accessorBaseType, arrayTypeName, syntheticClassName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* creates an instance of an ArrayCreator of the specified type */
|
||||||
|
static ArrayCreator getCreatorInstance(Class<?> elementType, int... dimensions) {
|
||||||
|
String elementTypeName = Java.internalName(elementType);
|
||||||
|
String syntheticClassName = "com/shautvast/reflective/array/ArrayCreator_"
|
||||||
|
+ Java.javaName(elementTypeName) + dimensions.length;
|
||||||
|
|
||||||
|
return creatorCache.computeIfAbsent(syntheticClassName,
|
||||||
|
k -> ArrayHandlerFactory.createSyntheticArrayCreator(elementTypeName, syntheticClassName, dimensions));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creates the ASM ClassNode for an ArrayCreator */
|
||||||
|
static ArrayCreator createSyntheticArrayCreator(String elementType, String name, int... dimensions) {
|
||||||
|
ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(ArrayCreator.class));
|
||||||
|
classNode.methods.add(createNewInstanceMethodNode(elementType, dimensions));
|
||||||
|
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||||
|
classNode.accept(classWriter);
|
||||||
|
|
||||||
|
byte[] byteArray = classWriter.toByteArray();
|
||||||
|
|
||||||
|
// try (FileOutputStream out = new FileOutputStream(name + ".class")) {
|
||||||
|
// out.write(byteArray);
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// }
|
||||||
|
|
||||||
|
ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (ArrayCreator) (ByteClassLoader.INSTANCE.loadClass(classNode.name).getConstructor().newInstance());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creates the ASM ClassNode for an ArrayAccessor */
|
||||||
|
static <T> T createSyntheticArrayAccessor(Class<T> accessorType, String arrayType, String name) {
|
||||||
|
ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(accessorType));
|
||||||
|
classNode.methods.add(createSetMethodNode(accessorType, arrayType));
|
||||||
|
classNode.methods.add(createGetMethodNode(accessorType, arrayType));
|
||||||
|
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||||
|
classNode.accept(classWriter);
|
||||||
|
|
||||||
|
byte[] byteArray = classWriter.toByteArray();
|
||||||
|
|
||||||
|
try (FileOutputStream out = new FileOutputStream("A.class")) {
|
||||||
|
out.write(byteArray);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return accessorType.cast(ByteClassLoader.INSTANCE.loadClass(classNode.name).getConstructor().newInstance());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creates the newInstance method for ArrayCreator classes */
|
||||||
|
private static MethodNode createNewInstanceMethodNode(String componentType, int[] dimensions) {
|
||||||
|
MethodNode methodNode = new MethodNode(ACC_PUBLIC,
|
||||||
|
"newInstance", "()Ljava/lang/Object;", null, null);
|
||||||
|
InsnList insns = methodNode.instructions;
|
||||||
|
Arrays.stream(dimensions).forEach(d -> insns.add(new LdcInsnNode(d)));
|
||||||
|
insns.add(new MultiANewArrayInsnNode(Java.createArrayType(componentType, dimensions), dimensions.length));
|
||||||
|
insns.add(new InsnNode(ARETURN));
|
||||||
|
return methodNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creates the set method for ArrayAccessor classes */
|
||||||
|
private static MethodNode createSetMethodNode(Class<?> accessorType, String arrayType) {
|
||||||
|
String elementType;
|
||||||
|
if (accessorType == ObjectArrayAccessor.class) {
|
||||||
|
elementType = "Ljava/lang/Object;";
|
||||||
|
} else {
|
||||||
|
elementType = arrayType.substring(1);
|
||||||
|
}
|
||||||
|
MethodNode methodNode = new MethodNode(ACC_PUBLIC,
|
||||||
|
"set", "(Ljava/lang/Object;I" + elementType + ")V", null, null);
|
||||||
|
int[] opcodes = arrayStoreInstructions(elementType);
|
||||||
|
InsnList insns = methodNode.instructions;
|
||||||
|
insns.add(new VarInsnNode(ALOAD, 1));
|
||||||
|
insns.add(new TypeInsnNode(CHECKCAST, arrayType));
|
||||||
|
insns.add(new VarInsnNode(ILOAD, 2));
|
||||||
|
insns.add(new VarInsnNode(opcodes[0], 3));
|
||||||
|
insns.add(new InsnNode(opcodes[1]));
|
||||||
|
insns.add(new InsnNode(RETURN));
|
||||||
|
|
||||||
|
return methodNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodNode createGetMethodNode(Class<?> accessorType, String arrayType) {
|
||||||
|
String elementType;
|
||||||
|
if (accessorType == ObjectArrayAccessor.class) {
|
||||||
|
elementType = "Ljava/lang/Object;";
|
||||||
|
} else {
|
||||||
|
elementType = arrayType.substring(1);
|
||||||
|
}
|
||||||
|
MethodNode methodNode = new MethodNode(ACC_PUBLIC,
|
||||||
|
"get", "(Ljava/lang/Object;I)" + elementType, null, null);
|
||||||
|
InsnList insns = methodNode.instructions;
|
||||||
|
int[] opcodes = arrayLoadAndReturnInstructions(elementType);
|
||||||
|
insns.add(new VarInsnNode(ALOAD, 1));
|
||||||
|
insns.add(new TypeInsnNode(CHECKCAST, arrayType));
|
||||||
|
insns.add(new VarInsnNode(ILOAD, 2));
|
||||||
|
insns.add(new InsnNode(opcodes[0]));
|
||||||
|
insns.add(new InsnNode(opcodes[1]));
|
||||||
|
|
||||||
|
return methodNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* gets the pair of appropriate load (type) and store (array of type) instructions
|
||||||
|
*/
|
||||||
|
private static int[] arrayStoreInstructions(String type) {
|
||||||
|
switch (type) {
|
||||||
|
case "B":
|
||||||
|
case "Z":
|
||||||
|
return new int[]{ILOAD, BASTORE};
|
||||||
|
case "S":
|
||||||
|
return new int[]{ILOAD, SASTORE};
|
||||||
|
case "I":
|
||||||
|
return new int[]{ILOAD, IASTORE};
|
||||||
|
case "J":
|
||||||
|
return new int[]{LLOAD, LASTORE};
|
||||||
|
case "F":
|
||||||
|
return new int[]{FLOAD, FASTORE};
|
||||||
|
case "D":
|
||||||
|
return new int[]{DLOAD, DASTORE};
|
||||||
|
case "C":
|
||||||
|
return new int[]{ILOAD, CASTORE};
|
||||||
|
default:
|
||||||
|
return new int[]{ALOAD, AASTORE};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* gets the pair of appropriate load (type) and store (array of type) instructions
|
||||||
|
*/
|
||||||
|
private static int[] arrayLoadAndReturnInstructions(String type) {
|
||||||
|
switch (type) {
|
||||||
|
case "B":
|
||||||
|
case "Z":
|
||||||
|
return new int[]{BALOAD, IRETURN};
|
||||||
|
case "I":
|
||||||
|
return new int[]{IALOAD, IRETURN};
|
||||||
|
case "S":
|
||||||
|
return new int[]{SALOAD, IRETURN};
|
||||||
|
case "C":
|
||||||
|
return new int[]{CALOAD, IRETURN};
|
||||||
|
case "J":
|
||||||
|
return new int[]{LALOAD, LRETURN};
|
||||||
|
case "F":
|
||||||
|
return new int[]{FALOAD, FRETURN};
|
||||||
|
case "D":
|
||||||
|
return new int[]{DALOAD, DRETURN};
|
||||||
|
default:
|
||||||
|
return new int[]{AALOAD, ARETURN};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty interface because you can't be generic over primitives in Java
|
||||||
|
*/
|
||||||
|
public interface ArrayAccessor {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class ArrayCreator {
|
||||||
|
|
||||||
|
public abstract Object newInstance();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class BooleanArrayAccessor implements ArrayAccessor {
|
||||||
|
public abstract void set(Object array, int index, boolean value);
|
||||||
|
public abstract boolean get(Object array, int index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class ByteArrayAccessor implements ArrayAccessor {
|
||||||
|
|
||||||
|
public abstract void set(Object array, int index, byte value);
|
||||||
|
|
||||||
|
public abstract byte get(Object array, int index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class CharArrayAccessor implements ArrayAccessor {
|
||||||
|
|
||||||
|
public abstract void set(Object array, int index, char value);
|
||||||
|
public abstract char get(Object array, int index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class DoubleArrayAccessor implements ArrayAccessor {
|
||||||
|
|
||||||
|
public abstract void set(Object array, int index, double value);
|
||||||
|
public abstract double get(Object array, int index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class FloatArrayAccessor implements ArrayAccessor {
|
||||||
|
|
||||||
|
public abstract void set(Object array, int index, float value);
|
||||||
|
|
||||||
|
public abstract float get(Object array, int index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class IntArrayAccessor implements ArrayAccessor {
|
||||||
|
public abstract void set(Object array, int index, int value);
|
||||||
|
|
||||||
|
public abstract int get(Object array, int index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class LongArrayAccessor implements ArrayAccessor {
|
||||||
|
|
||||||
|
public abstract void set(Object array, int index, long value);
|
||||||
|
|
||||||
|
public abstract long get(Object array, int index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class ObjectArrayAccessor implements ArrayAccessor {
|
||||||
|
public abstract void set(Object array, int index, Object value);
|
||||||
|
public abstract Object get(Object array, int index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.github.shautvast.reflective.array.base;
|
||||||
|
|
||||||
|
public abstract class ShortArrayAccessor implements ArrayAccessor {
|
||||||
|
|
||||||
|
public abstract void set(Object array, int index, short value);
|
||||||
|
|
||||||
|
public abstract short get(Object array, int index);
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ public class ByteClassLoader extends ClassLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addClass(String name, byte[] bytecode) {
|
public void addClass(String name, byte[] bytecode) {
|
||||||
Class<?> classDef = defineClass(name, bytecode, 0, bytecode.length);
|
Class<?> classDef = defineClass(null, bytecode, 0, bytecode.length);
|
||||||
classes.put(name, classDef);
|
classes.put(name, classDef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import org.objectweb.asm.ClassReader;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static java.lang.reflect.Array.newInstance;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* common utils not for external use
|
* common utils not for external use
|
||||||
*/
|
*/
|
||||||
|
|
@ -22,6 +20,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("\\.", "/");
|
||||||
|
|
@ -106,49 +105,116 @@ public class Java {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Class<?> getClassFromDescriptor(String descriptor) {
|
public static Class<?> toClass(String descriptor) {
|
||||||
int arrayDims = 0;
|
|
||||||
while (descriptor.startsWith("[")) {
|
|
||||||
arrayDims++;
|
|
||||||
descriptor = descriptor.substring(1);
|
|
||||||
} //could be cheaper
|
|
||||||
|
|
||||||
if (descriptor.startsWith("L") && descriptor.endsWith(";")) {
|
|
||||||
try {
|
try {
|
||||||
String className = descriptor.substring(1, descriptor.length() - 1).replaceAll("/", ".");
|
if (descriptor.length() == 1) {
|
||||||
Class<?> clazz = Class.forName(className);
|
return mapIfPrimitive(descriptor.charAt(0));
|
||||||
if (arrayDims > 0) {
|
|
||||||
clazz = newInstance(clazz, new int[arrayDims]).getClass();
|
|
||||||
}
|
}
|
||||||
return clazz;
|
if (!descriptor.startsWith("[") && descriptor.endsWith(";")) {
|
||||||
|
descriptor = descriptor.substring(0, descriptor.length() - 1);
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
while (descriptor.charAt(i) == '[' && i < 256) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
assert i < 256;
|
||||||
|
String dims = descriptor.substring(0, i);
|
||||||
|
String type = descriptor.substring(i);
|
||||||
|
if (i == 0 && type.startsWith("L")) {
|
||||||
|
type = type.substring(1);
|
||||||
|
}
|
||||||
|
type = Java.externalName(type);
|
||||||
|
Class<?> aClass = Class.forName(dims + type);
|
||||||
|
return aClass;
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
throw new RuntimeException(e); // not supposed to happen
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
char typeChar = descriptor.charAt(0);
|
|
||||||
boolean isArray = arrayDims != 0;
|
private static Class<?> mapIfPrimitive(char descriptor) {
|
||||||
switch (typeChar) {
|
switch (descriptor) {
|
||||||
case 'B':
|
case 'B':
|
||||||
return isArray ? newInstance(byte.class, new int[arrayDims]).getClass() : byte.class;
|
return 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':
|
case 'S':
|
||||||
return isArray ? newInstance(short.class, new int[arrayDims]).getClass() : short.class;
|
return short.class;
|
||||||
|
case 'I':
|
||||||
|
return int.class;
|
||||||
|
case 'J':
|
||||||
|
return long.class;
|
||||||
|
case 'F':
|
||||||
|
return float.class;
|
||||||
|
case 'D':
|
||||||
|
return double.class;
|
||||||
|
case 'C':
|
||||||
|
return char.class;
|
||||||
case 'Z':
|
case 'Z':
|
||||||
return isArray ? newInstance(boolean.class, new int[arrayDims]).getClass() : boolean.class;
|
return boolean.class;
|
||||||
case 'V':
|
case 'V':
|
||||||
return Void.class;
|
return Void.class;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException(new ClassNotFoundException("unknown descriptor: " + descriptor)); //must not happen
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* strips all disallowed characters from a classname */
|
||||||
|
public static String javaName(String arrayTypeName) {
|
||||||
|
return arrayTypeName
|
||||||
|
.replaceAll("[/.\\[;]", "")
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String mapPrimitiveOrArrayName(String type) {
|
||||||
|
switch (type) {
|
||||||
|
case "byte":
|
||||||
|
return "B";
|
||||||
|
case "short":
|
||||||
|
return "S";
|
||||||
|
case "int":
|
||||||
|
return "I";
|
||||||
|
case "long":
|
||||||
|
return "J";
|
||||||
|
case "float":
|
||||||
|
return "F";
|
||||||
|
case "double":
|
||||||
|
return "D";
|
||||||
|
case "char":
|
||||||
|
return "C";
|
||||||
|
case "boolean":
|
||||||
|
return "Z";
|
||||||
|
default:
|
||||||
|
if (type.startsWith("[") || type.contains("/")) {
|
||||||
|
return type;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(type + "?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getNumDimensions(Class<?> arrayType) {
|
||||||
|
return getNumDimensions(arrayType.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getNumDimensions(String arrayName) {
|
||||||
|
int i = 0;
|
||||||
|
while (arrayName.charAt(i) == '[') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String createArrayType(String componentType, int[] dimensions) {
|
||||||
|
StringBuilder s = new StringBuilder();
|
||||||
|
s.append("[".repeat(dimensions.length));
|
||||||
|
boolean isObject = !componentType.startsWith("[") && componentType.contains("/");
|
||||||
|
if (isObject) {
|
||||||
|
s.append("L");
|
||||||
|
s.append(componentType);
|
||||||
|
} else {
|
||||||
|
s.append(Java.mapPrimitiveOrArrayName(componentType));
|
||||||
|
}
|
||||||
|
if (isObject) {
|
||||||
|
s.append(";");
|
||||||
|
}
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,8 @@ public class Result<T> {
|
||||||
return new Result<>(null, error);
|
return new Result<>(null, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isOk() {
|
||||||
|
return error == null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package com.github.shautvast.reflective;
|
|
||||||
|
|
||||||
public class DummyInvoker extends AbstractInvoker {
|
|
||||||
|
|
||||||
public Object invoke(Object instance, Object... arguments) {
|
|
||||||
((ReflectiveTest.Dummy) instance).setName((String)arguments[0]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
package com.github.shautvast.reflective;
|
package com.github.shautvast.reflective;
|
||||||
|
|
||||||
import com.github.shautvast.rusty.Panic;
|
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.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;
|
||||||
|
|
||||||
|
|
@ -15,17 +18,13 @@ public class ReflectiveTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMethods() {
|
void testMethods() {
|
||||||
Dummy dummy = new Dummy("bar");
|
Dummy dummy = getDummy();
|
||||||
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(5, 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());
|
||||||
|
|
@ -36,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());
|
||||||
|
|
@ -50,39 +49,89 @@ public class ReflectiveTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testInvokeGetter() {
|
void testInvokeGetter() {
|
||||||
Dummy dummy = new Dummy("bar");
|
Dummy dummy = getDummy();
|
||||||
MetaMethod getName = Reflective.getMetaClass(dummy.getClass()).getMethod("getName").orElseGet(Assertions::fail);
|
MetaMethod getStringValue = Reflective.getMetaClass(dummy.getClass()).getMethod("getStringValue").orElseGet(Assertions::fail);
|
||||||
|
|
||||||
assertThrows(Panic.class, () -> getName.invoke("foo").unwrap());
|
// passing "foo" as the instance is not allowed
|
||||||
assertEquals("bar", getName.invoke(dummy).unwrap());
|
assertThrows(Panic.class, () -> getStringValue.invoke("foo").unwrap());
|
||||||
|
// we should pass a valid dummy instance
|
||||||
|
assertEquals("don't panic!", getStringValue.invoke(dummy).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testInvokeSetter() {
|
void testInvokeSetters() {
|
||||||
Dummy dummy = new Dummy("bar");
|
Dummy dummy = getDummy();
|
||||||
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
|
||||||
|
void testInvocationExceptionHappened() {
|
||||||
|
Dummy dummy = getDummy();
|
||||||
|
MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass());
|
||||||
|
MetaMethod throwEx = metaForClass.getMethod("throwEx").orElseGet(Assertions::fail);
|
||||||
|
|
||||||
|
Result<Void> result = throwEx.invoke(dummy, "foo");
|
||||||
|
assertFalse(result.isOk());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dummy getDummy() {
|
||||||
|
return new Dummy((byte) 42, (short) 43,
|
||||||
|
44, 45, 46.0F, 47.0,
|
||||||
|
'D', true, "don't panic!",
|
||||||
|
new byte[1][1], new String[]{"Please don't touch that button"});
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
private byte[][] byteArrayValue;
|
||||||
public void setName(String name) {
|
private String[] stringArrayValue;
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
|
|
@ -94,8 +143,14 @@ 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
package com.github.shautvast.reflective.array;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class ArrayFactoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate1DimShortArray() {
|
||||||
|
Object o = ArrayFactory.newInstance(short.class, 1);
|
||||||
|
assertTrue(o instanceof short[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate2DimIntArray() {
|
||||||
|
int[][] ints = (int[][]) ArrayFactory.newInstance(int[].class, 1);
|
||||||
|
assertEquals(1, ints.length);
|
||||||
|
ints[0] = new int[1]; // will fail if array is not correctly created
|
||||||
|
assertEquals(1, ints[0].length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate3DimStringArray() {
|
||||||
|
String[][][] array = (String[][][]) ArrayFactory.newInstance(String.class, 6, 7, 8);
|
||||||
|
assertEquals(6, array.length);
|
||||||
|
assertEquals(7, array[0].length);
|
||||||
|
assertEquals(8, array[0][0].length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetObject() {
|
||||||
|
String[] strings = new String[1];
|
||||||
|
ArrayFactory.set(strings, 0, "helloworld");
|
||||||
|
assertArrayEquals(new String[]{"helloworld"}, strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetInt() {
|
||||||
|
int[] ints = new int[1];
|
||||||
|
ArrayFactory.setInt(ints, 0, 11);
|
||||||
|
assertArrayEquals(new int[]{11}, ints);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetByte() {
|
||||||
|
byte[] bytes = new byte[1];
|
||||||
|
ArrayFactory.setByte(bytes, 0, (byte) 11);
|
||||||
|
assertArrayEquals(new byte[]{11}, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetShort() {
|
||||||
|
short[] shorts = new short[1];
|
||||||
|
ArrayFactory.setShort(shorts, 0, (short) 11);
|
||||||
|
assertArrayEquals(new short[]{11}, shorts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetLong() {
|
||||||
|
long[] longs = new long[1];
|
||||||
|
ArrayFactory.setLong(longs, 0, 11L);
|
||||||
|
assertArrayEquals(new long[]{11}, longs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetFloat() {
|
||||||
|
float[] floats = new float[1];
|
||||||
|
ArrayFactory.setFloat(floats, 0, 11.1F);
|
||||||
|
assertArrayEquals(new float[]{11.1F}, floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetDouble() {
|
||||||
|
double[] doubles = new double[1];
|
||||||
|
ArrayFactory.setDouble(doubles, 0, 11.1D);
|
||||||
|
assertArrayEquals(new double[]{11.1D}, doubles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetBoolean() {
|
||||||
|
boolean[] booleans = new boolean[]{false};
|
||||||
|
ArrayFactory.setBoolean(booleans, 0, true);
|
||||||
|
assertArrayEquals(new boolean[]{true}, booleans);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetChar() {
|
||||||
|
char[] chars = new char[]{'C'};
|
||||||
|
ArrayFactory.setChar(chars, 0, 'D');
|
||||||
|
assertArrayEquals(new char[]{'D'}, chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetFromObjectArray() {
|
||||||
|
String[] strings = new String[]{"helloworld"};
|
||||||
|
Object o = ArrayFactory.get(strings, 0);
|
||||||
|
assertTrue(o instanceof String);
|
||||||
|
String string = (String) o;
|
||||||
|
assertEquals("helloworld", string);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetFromByteArray() {
|
||||||
|
byte[] bytes = new byte[]{17};
|
||||||
|
byte b = ArrayFactory.getByte(bytes, 0);
|
||||||
|
assertEquals(17, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetFromShortArray() {
|
||||||
|
short[] shorts = new short[]{17};
|
||||||
|
short s = ArrayFactory.getShort(shorts, 0);
|
||||||
|
assertEquals(17, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetFromIntArray() {
|
||||||
|
int[] ints = new int[]{17};
|
||||||
|
int i = ArrayFactory.getInt(ints, 0);
|
||||||
|
assertEquals(17, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetFromLongArray() {
|
||||||
|
long[] longs = new long[]{17};
|
||||||
|
long l = ArrayFactory.getLong(longs, 0);
|
||||||
|
assertEquals(17, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetFromCharArray() {
|
||||||
|
char[] chars = new char[]{17};
|
||||||
|
char c = ArrayFactory.getChar(chars, 0);
|
||||||
|
assertEquals(17, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetFromBooleanArray() {
|
||||||
|
boolean[] booleans = new boolean[]{true};
|
||||||
|
boolean b = ArrayFactory.getBoolean(booleans, 0);
|
||||||
|
assertEquals(true, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetFromFloatArray() {
|
||||||
|
float[] floats = new float[]{17.5F};
|
||||||
|
float f = ArrayFactory.getFloat(floats, 0);
|
||||||
|
assertEquals(17.5, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetFromDoubleArray() {
|
||||||
|
double[] doubles = new double[]{17.5F};
|
||||||
|
double d = ArrayFactory.getDouble(doubles, 0);
|
||||||
|
assertEquals(17.5, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void arrayIndexOutOfBoundsException() {
|
||||||
|
double[] doubles = new double[]{};
|
||||||
|
assertThrows(ArrayIndexOutOfBoundsException.class, () -> ArrayFactory.getDouble(doubles, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void notAnArray() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ArrayFactory.get("foo", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void arrayIsNull() {
|
||||||
|
assertThrows(NullPointerException.class, () -> ArrayFactory.get(null, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue