completed the ArrayFactory Setters

This commit is contained in:
Shautvast 2023-09-02 16:56:11 +02:00
parent 31f2b7c0f5
commit 207d91cd6c
17 changed files with 395 additions and 124 deletions

View file

@ -61,7 +61,6 @@ public class InvokerFactory {
// put argument on the stack // put argument on the stack
insns.add(new InsnNode(AALOAD)); insns.add(new InsnNode(AALOAD));
String type = internalName(mapPrimitiveToWrapper(method.getParameters().get(i).getType()).getName()); String type = internalName(mapPrimitiveToWrapper(method.getParameters().get(i).getType()).getName());
System.out.println("--" + type);
insns.add(new TypeInsnNode(CHECKCAST, type)); insns.add(new TypeInsnNode(CHECKCAST, type));
unwrapPrimitive(method.getParameters().get(i), insns); unwrapPrimitive(method.getParameters().get(i), insns);
} }
@ -164,7 +163,6 @@ public class InvokerFactory {
} }
private static Class<?> mapPrimitiveToWrapper(Class<?> type) { private static Class<?> mapPrimitiveToWrapper(Class<?> type) {
System.out.println(type);
if (type == int.class) { if (type == int.class) {
return Integer.class; return Integer.class;
} else if (type == byte.class) { } else if (type == byte.class) {

View file

@ -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;
}
}
}

View file

@ -1,24 +1,18 @@
package com.github.shautvast.reflective.array; package com.github.shautvast.reflective.array;
import com.github.shautvast.reflective.java.ASM; import com.github.shautvast.reflective.array.base.*;
import com.github.shautvast.reflective.java.ByteClassLoader;
import com.github.shautvast.reflective.java.Java; import com.github.shautvast.reflective.java.Java;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.*;
import java.util.Arrays; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ARETURN;
/** /**
* Factory class for dynamically creating arrays * Factory class for dynamically working with arrays
*/ */
public class ArrayFactory { public class ArrayFactory {
private static final ConcurrentMap<String, ArrayCreator> cache = new ConcurrentHashMap<>(); private static final Map<String, ArrayCreator> creatorCache = new ConcurrentHashMap<>();
private static final Map<Class<?>, Map<String, Object>> setterCache = new ConcurrentHashMap<>();
/** /**
* Creates a new array of the specified type and dimensions * Creates a new array of the specified type and dimensions
@ -27,69 +21,127 @@ public class ArrayFactory {
* @param dimensions array of ints, ie {10} means 1 dimension, length 10. {10,20} means 2 dimensions, size 10 by 20 * @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 * @return an object that you can cast to the expected array type
*/ */
public static Object newArray(Class<?> elementType, int... dimensions) { public static Object newInstance(Class<?> elementType, int... dimensions) {
return 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) {
getSetterInstance(ObjectArraySetter.class, typeChecked(array)).set(array, index, value);
}
/**
* 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 set(Object array, int index, int value) {
getSetterInstance(IntArraySetter.class, typeChecked(array)).set(array, index, value);
}
/**
* 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 set(Object array, int index, byte value) {
getSetterInstance(ByteArraySetter.class, typeChecked(array)).set(array, index, value);
}
/**
* 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 set(Object array, int index, short value) {
getSetterInstance(ShortArraySetter.class, typeChecked(array)).set(array, index, value);
}
/**
* 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 set(Object array, int index, long value) {
getSetterInstance(LongArraySetter.class, typeChecked(array)).set(array, index, value);
}
/**
* 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 set(Object array, int index, float value) {
getSetterInstance(FloatArraySetter.class, typeChecked(array)).set(array, index, value);
}
/**
* 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 set(Object array, int index, double value) {
getSetterInstance(DoubleArraySetter.class, typeChecked(array)).set(array, index, value);
}
public static void set(Object array, int index, char value) {
getSetterInstance(CharArraySetter.class, typeChecked(array)).set(array, index, value);
}
public static void set(Object array, int index, boolean value) {
getSetterInstance(BooleanArraySetter.class, typeChecked(array)).set(array, index, value);
}
private static Class<?> typeChecked(Object array) {
Class<?> arrayType = array.getClass();
if (!arrayType.isArray()) {
throw new IllegalArgumentException("This is not an array");
}
return arrayType;
}
@SuppressWarnings("unchecked")
private static <T> T getSetterInstance(Class<T> setterBaseType, Class<?> arrayType) {
String arrayTypeName = Java.internalName(arrayType);
String syntheticClassName = getSyntheticClassName(arrayType, arrayTypeName);
return (T) setterCache.computeIfAbsent(setterBaseType, k -> new ConcurrentHashMap<>()).
computeIfAbsent(syntheticClassName,
k -> AsmArrayFactory.createSyntheticArraySetter(setterBaseType, arrayTypeName, syntheticClassName));
}
private static String getSyntheticClassName(Class<?> arrayType, String arrayTypeName) {
return "com/shautvast/reflective/array/ArraySetter_"
+ javaName(arrayTypeName) + Java.getNumDimensions(arrayType);
}
private static ArrayCreator getCreatorInstance(Class<?> elementType, int... dimensions) {
String elementTypeName = Java.internalName(elementType); String elementTypeName = Java.internalName(elementType);
String syntheticClassName = syntheticClassName(elementTypeName, dimensions); String syntheticClassName = "com/shautvast/reflective/array/ArrayCreator_"
+ javaName(elementTypeName) + dimensions.length;
return cache.computeIfAbsent(syntheticClassName, return creatorCache.computeIfAbsent(syntheticClassName,
k -> createSyntheticArrayCreator(elementTypeName, syntheticClassName, dimensions)).newInstance(); k -> AsmArrayFactory.createSyntheticArrayCreator(elementTypeName, syntheticClassName, dimensions));
} }
private static String syntheticClassName(String elementTypeName, int[] dimensions) { private static String javaName(String arrayTypeName) {
return "RAC" + elementTypeName return arrayTypeName
.replaceAll("[/.\\[;]", "") .replaceAll("[/.\\[;]", "")
.toLowerCase() + "L" + dimensions.length; .toLowerCase();
} }
private static ArrayCreator createSyntheticArrayCreator(String componentType, String name, int... dimensions) {
ClassNode classNode = createASMClassNode(componentType, name, 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);
}
}
private static ClassNode createASMClassNode(String componentType, String name, int[] dimensions) {
ClassNode classNode = ASM.createDefaultClassNode(name,
Java.internalName(ArrayCreator.class));
MethodNode methodNode = new MethodNode(ACC_PUBLIC,
"newInstance", "()Ljava/lang/Object;", null, null);
classNode.methods.add(methodNode);
InsnList insns = methodNode.instructions;
Arrays.stream(dimensions).forEach(d -> insns.add(new LdcInsnNode(d)));
insns.add(new MultiANewArrayInsnNode(createArrayType(componentType, dimensions), dimensions.length));
insns.add(new InsnNode(ARETURN));
return classNode;
}
private 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();
}
} }

View file

@ -0,0 +1,120 @@
package com.github.shautvast.reflective.array;
import com.github.shautvast.reflective.array.base.ArrayCreator;
import com.github.shautvast.reflective.array.base.ObjectArraySetter;
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 static org.objectweb.asm.Opcodes.*;
class AsmArrayFactory {
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);
}
}
static <T> T createSyntheticArraySetter(Class<T> setterType, String arrayType, String name) {
ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(setterType));
classNode.methods.add(createSetMethodNode(setterType, 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 setterType.cast(ByteClassLoader.INSTANCE.loadClass(classNode.name).getConstructor().newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
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;
}
private static MethodNode createSetMethodNode(Class<?> setterType, String arrayType) {
String elementType;
if (setterType == ObjectArraySetter.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 = getInstructions(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;
}
/*
* gets the pair of appropriate load (type) and store (array of type) instructions
*/
private static int[] getInstructions(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};
}
}
}

View file

@ -1,4 +1,4 @@
package com.github.shautvast.reflective.array; package com.github.shautvast.reflective.array.base;
public abstract class ArrayCreator { public abstract class ArrayCreator {

View file

@ -0,0 +1,6 @@
package com.github.shautvast.reflective.array.base;
public abstract class BooleanArraySetter {
public abstract void set(Object array, int index, boolean value);
}

View file

@ -0,0 +1,6 @@
package com.github.shautvast.reflective.array.base;
public abstract class ByteArraySetter {
public abstract void set(Object array, int index, byte value);
}

View file

@ -0,0 +1,6 @@
package com.github.shautvast.reflective.array.base;
public abstract class CharArraySetter {
public abstract void set(Object array, int index, char value);
}

View file

@ -0,0 +1,6 @@
package com.github.shautvast.reflective.array.base;
public abstract class DoubleArraySetter {
public abstract void set(Object array, int index, double value);
}

View file

@ -0,0 +1,6 @@
package com.github.shautvast.reflective.array.base;
public abstract class FloatArraySetter {
public abstract void set(Object array, int index, float value);
}

View file

@ -0,0 +1,5 @@
package com.github.shautvast.reflective.array.base;
public abstract class IntArraySetter {
public abstract void set(Object array, int index, int value);
}

View file

@ -0,0 +1,6 @@
package com.github.shautvast.reflective.array.base;
public abstract class LongArraySetter {
public abstract void set(Object array, int index, long value);
}

View file

@ -0,0 +1,5 @@
package com.github.shautvast.reflective.array.base;
public abstract class ObjectArraySetter {
public abstract void set(Object array, int index, Object value);
}

View file

@ -0,0 +1,6 @@
package com.github.shautvast.reflective.array.base;
public abstract class ShortArraySetter {
public abstract void set(Object array, int index, short value);
}

View file

@ -106,7 +106,6 @@ public class Java {
} }
public static Class<?> toClass(String descriptor) { public static Class<?> toClass(String descriptor) {
System.out.println("desc;" + descriptor);
try { try {
if (descriptor.length() == 1) { if (descriptor.length() == 1) {
return mapIfPrimitive(descriptor.charAt(0)); return mapIfPrimitive(descriptor.charAt(0));
@ -126,7 +125,6 @@ public class Java {
} }
type = Java.externalName(type); type = Java.externalName(type);
Class<?> aClass = Class.forName(dims + type); Class<?> aClass = Class.forName(dims + type);
System.out.println(aClass);
return aClass; return aClass;
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -154,7 +152,6 @@ public class Java {
case 'V': case 'V':
return Void.class; return Void.class;
default: default:
System.out.println("desc:" + descriptor);
return null; return null;
} }
} }
@ -185,4 +182,32 @@ public class Java {
} }
} }
} }
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();
}
} }

View file

@ -1,10 +0,0 @@
package com.github.shautvast.reflective;
@SuppressWarnings("ALL")
public class DummyInvoker extends AbstractInvoker {
public Object invoke(Object instance, Object... arguments) {
((ReflectiveTest.Dummy) instance).setByteArrayValue((byte[][])arguments[0]);
return null;
}
}

View file

@ -2,30 +2,92 @@ package com.github.shautvast.reflective.array;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ArraysTest { public class ArraysTest {
@Test @Test
void test1Dim() { void testCreate1DimShortArray() {
Object o = ArrayFactory.newArray(String.class, 1); Object o = ArrayFactory.newInstance(short.class, 1);
assertTrue(o instanceof String[]); assertTrue(o instanceof short[]);
} }
@Test @Test
void test2Dims() { void testCreate2DimIntArray() {
int[][] ints = (int[][])ArrayFactory.newArray(int[].class, 1); int[][] ints = (int[][]) ArrayFactory.newInstance(int[].class, 1);
assertEquals(1, ints.length); assertEquals(1, ints.length);
ints[0] = new int[1]; // will fail if array is not correctly created ints[0] = new int[1]; // will fail if array is not correctly created
assertEquals(1, ints[0].length); assertEquals(1, ints[0].length);
} }
@Test @Test
void test3Dims() { void testCreate3DimStringArray() {
String[][][] array = (String[][][]) ArrayFactory.newArray(String.class, 6, 7, 8); String[][][] array = (String[][][]) ArrayFactory.newInstance(String.class, 6, 7, 8);
assertEquals(6, array.length); assertEquals(6, array.length);
assertEquals(7, array[0].length); assertEquals(7, array[0].length);
assertEquals(8, array[0][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.set(ints, 0, 11);
assertArrayEquals(new int[]{11}, ints);
}
@Test
void testSetByte() {
byte[] bytes = new byte[1];
ArrayFactory.set(bytes, 0, (byte) 11);
assertArrayEquals(new byte[]{11}, bytes);
}
@Test
void testSetShort() {
short[] shorts = new short[1];
ArrayFactory.set(shorts, 0, (short) 11);
assertArrayEquals(new short[]{11}, shorts);
}
@Test
void testSetLong() {
long[] longs = new long[1];
ArrayFactory.set(longs, 0, 11L);
assertArrayEquals(new long[]{11}, longs);
}
@Test
void testSetFloat() {
float[] floats = new float[1];
ArrayFactory.set(floats, 0, 11.1F);
assertArrayEquals(new float[]{11.1F}, floats);
}
@Test
void testSetDouble() {
double[] doubles = new double[1];
ArrayFactory.set(doubles, 0, 11.1D);
assertArrayEquals(new double[]{11.1D}, doubles);
}
@Test
void testSetBoolean() {
boolean[] booleans = new boolean[]{false};
ArrayFactory.set(booleans, 0, true);
assertArrayEquals(new boolean[]{true}, booleans);
}
@Test
void testSetChar() {
char[] chars = new char[]{'C'};
ArrayFactory.set(chars, 0, 'D');
assertArrayEquals(new char[]{'D'}, chars);
}
} }