reshuffling methods, adding docs

This commit is contained in:
Shautvast 2023-09-02 17:27:42 +02:00
parent 207d91cd6c
commit 021304a5d8
12 changed files with 84 additions and 60 deletions

View file

@ -1,18 +1,12 @@
package com.github.shautvast.reflective.array; package com.github.shautvast.reflective.array;
import com.github.shautvast.reflective.array.base.*; import com.github.shautvast.reflective.array.base.*;
import com.github.shautvast.reflective.java.Java;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* Factory class for dynamically working with arrays * Public interface for dynamically working with arrays (create, set and get operations)
*/ */
public class ArrayFactory { public class ArrayFactory {
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
@ -22,37 +16,40 @@ public class ArrayFactory {
* @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 newInstance(Class<?> elementType, int... dimensions) { public static Object newInstance(Class<?> elementType, int... dimensions) {
return getCreatorInstance(elementType, dimensions).newInstance(); return ArrayHandlerFactory.getCreatorInstance(elementType, dimensions).newInstance();
} }
/** /**
* Sets an Object value on an array * Sets an Object value on an array
*
* @param array the array on which the value is set * @param array the array on which the value is set
* @param index the array index * @param index the array index
* @param value the value to set * @param value the value to set
*/ */
public static void set(Object array, int index, Object value) { public static void set(Object array, int index, Object value) {
getSetterInstance(ObjectArraySetter.class, typeChecked(array)).set(array, index, value); ArrayHandlerFactory.getSetterInstance(ObjectArraySetter.class, typeChecked(array)).set(array, index, value);
} }
/** /**
* Sets an int value on an array * Sets an int value on an array
*
* @param array the array on which the value is set * @param array the array on which the value is set
* @param index the array index * @param index the array index
* @param value the value to set * @param value the value to set
*/ */
public static void set(Object array, int index, int value) { public static void set(Object array, int index, int value) {
getSetterInstance(IntArraySetter.class, typeChecked(array)).set(array, index, value); ArrayHandlerFactory.getSetterInstance(IntArraySetter.class, typeChecked(array)).set(array, index, value);
} }
/** /**
* Sets a byte value on an array * Sets a byte value on an array
*
* @param array the array on which the value is set * @param array the array on which the value is set
* @param index the array index * @param index the array index
* @param value the value to set * @param value the value to set
*/ */
public static void set(Object array, int index, byte value) { public static void set(Object array, int index, byte value) {
getSetterInstance(ByteArraySetter.class, typeChecked(array)).set(array, index, value); ArrayHandlerFactory.getSetterInstance(ByteArraySetter.class, typeChecked(array)).set(array, index, value);
} }
/** /**
@ -63,7 +60,7 @@ public class ArrayFactory {
* @param value the value to set * @param value the value to set
*/ */
public static void set(Object array, int index, short value) { public static void set(Object array, int index, short value) {
getSetterInstance(ShortArraySetter.class, typeChecked(array)).set(array, index, value); ArrayHandlerFactory.getSetterInstance(ShortArraySetter.class, typeChecked(array)).set(array, index, value);
} }
/** /**
@ -74,7 +71,7 @@ public class ArrayFactory {
* @param value the value to set * @param value the value to set
*/ */
public static void set(Object array, int index, long value) { public static void set(Object array, int index, long value) {
getSetterInstance(LongArraySetter.class, typeChecked(array)).set(array, index, value); ArrayHandlerFactory.getSetterInstance(LongArraySetter.class, typeChecked(array)).set(array, index, value);
} }
/** /**
@ -85,7 +82,7 @@ public class ArrayFactory {
* @param value the value to set * @param value the value to set
*/ */
public static void set(Object array, int index, float value) { public static void set(Object array, int index, float value) {
getSetterInstance(FloatArraySetter.class, typeChecked(array)).set(array, index, value); ArrayHandlerFactory.getSetterInstance(FloatArraySetter.class, typeChecked(array)).set(array, index, value);
} }
/** /**
@ -96,17 +93,20 @@ public class ArrayFactory {
* @param value the value to set * @param value the value to set
*/ */
public static void set(Object array, int index, double value) { public static void set(Object array, int index, double value) {
getSetterInstance(DoubleArraySetter.class, typeChecked(array)).set(array, index, value); ArrayHandlerFactory.getSetterInstance(DoubleArraySetter.class, typeChecked(array)).set(array, index, value);
} }
public static void set(Object array, int index, char value) { public static void set(Object array, int index, char value) {
getSetterInstance(CharArraySetter.class, typeChecked(array)).set(array, index, value); ArrayHandlerFactory.getSetterInstance(CharArraySetter.class, typeChecked(array)).set(array, index, value);
} }
public static void set(Object array, int index, boolean value) { public static void set(Object array, int index, boolean value) {
getSetterInstance(BooleanArraySetter.class, typeChecked(array)).set(array, index, value); ArrayHandlerFactory.getSetterInstance(BooleanArraySetter.class, typeChecked(array)).set(array, index, value);
} }
/*
* Only checks if object is an array, not the type of that array. TODO
*/
private static Class<?> typeChecked(Object array) { private static Class<?> typeChecked(Object array) {
Class<?> arrayType = array.getClass(); Class<?> arrayType = array.getClass();
if (!arrayType.isArray()) { if (!arrayType.isArray()) {
@ -114,34 +114,4 @@ public class ArrayFactory {
} }
return arrayType; 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 syntheticClassName = "com/shautvast/reflective/array/ArrayCreator_"
+ javaName(elementTypeName) + dimensions.length;
return creatorCache.computeIfAbsent(syntheticClassName,
k -> AsmArrayFactory.createSyntheticArrayCreator(elementTypeName, syntheticClassName, dimensions));
}
private static String javaName(String arrayTypeName) {
return arrayTypeName
.replaceAll("[/.\\[;]", "")
.toLowerCase();
}
} }

View file

@ -1,6 +1,7 @@
package com.github.shautvast.reflective.array; package com.github.shautvast.reflective.array;
import com.github.shautvast.reflective.array.base.ArrayCreator; import com.github.shautvast.reflective.array.base.ArrayCreator;
import com.github.shautvast.reflective.array.base.ArraySetter;
import com.github.shautvast.reflective.array.base.ObjectArraySetter; import com.github.shautvast.reflective.array.base.ObjectArraySetter;
import com.github.shautvast.reflective.java.ASM; import com.github.shautvast.reflective.java.ASM;
import com.github.shautvast.reflective.java.ByteClassLoader; import com.github.shautvast.reflective.java.ByteClassLoader;
@ -11,10 +12,55 @@ import org.objectweb.asm.tree.*;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static org.objectweb.asm.Opcodes.*; import static org.objectweb.asm.Opcodes.*;
class AsmArrayFactory { class ArrayHandlerFactory {
/* cache for the compiled creator classes */
private static final Map<String, ArrayCreator> creatorCache = new ConcurrentHashMap<>();
/* Cache for the compiled setter classes.
* The outer Map contains the ArraySetter type (some primitive or Object Setter)
* That maps to the concrete calculated ArraySetter instance name which maps to the instance itself.
* TODO see if this can be optimized
*/
private static final Map<Class<? extends ArraySetter>, Map<String, Object>> setterCache = new ConcurrentHashMap<>();
/*
* generic method for creating array setters (primitives and objects)
*/
@SuppressWarnings("unchecked")
static <T extends ArraySetter> T getSetterInstance(Class<T> setterBaseType, Class<?> arrayType) {
String arrayTypeName = Java.internalName(arrayType);
String syntheticClassName = "com/shautvast/reflective/array/ArraySetter_"
+ javaName(arrayTypeName) + Java.getNumDimensions(arrayType);
return (T) setterCache.computeIfAbsent(setterBaseType, k -> new ConcurrentHashMap<>()).
computeIfAbsent(syntheticClassName,
k -> ArrayHandlerFactory.createSyntheticArraySetter(setterBaseType, 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_"
+ javaName(elementTypeName) + dimensions.length;
return creatorCache.computeIfAbsent(syntheticClassName,
k -> ArrayHandlerFactory.createSyntheticArrayCreator(elementTypeName, syntheticClassName, dimensions));
}
/* strips all disallowed characters from a classname */
private static String javaName(String arrayTypeName) {
return arrayTypeName
.replaceAll("[/.\\[;]", "")
.toLowerCase();
}
/* Creates the ASM ClassNode for an ArrayCreator */
static ArrayCreator createSyntheticArrayCreator(String elementType, String name, int... dimensions) { static ArrayCreator createSyntheticArrayCreator(String elementType, String name, int... dimensions) {
ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(ArrayCreator.class)); ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(ArrayCreator.class));
classNode.methods.add(createNewInstanceMethodNode(elementType, dimensions)); classNode.methods.add(createNewInstanceMethodNode(elementType, dimensions));
@ -38,6 +84,7 @@ class AsmArrayFactory {
} }
} }
/* Creates the ASM ClassNode for an ArraySetter */
static <T> T createSyntheticArraySetter(Class<T> setterType, String arrayType, String name) { static <T> T createSyntheticArraySetter(Class<T> setterType, String arrayType, String name) {
ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(setterType)); ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(setterType));
classNode.methods.add(createSetMethodNode(setterType, arrayType)); classNode.methods.add(createSetMethodNode(setterType, arrayType));
@ -61,6 +108,7 @@ class AsmArrayFactory {
} }
} }
/* Creates the newInstance method for ArrayCreator classes */
private static MethodNode createNewInstanceMethodNode(String componentType, int[] dimensions) { private static MethodNode createNewInstanceMethodNode(String componentType, int[] dimensions) {
MethodNode methodNode = new MethodNode(ACC_PUBLIC, MethodNode methodNode = new MethodNode(ACC_PUBLIC,
"newInstance", "()Ljava/lang/Object;", null, null); "newInstance", "()Ljava/lang/Object;", null, null);
@ -71,6 +119,7 @@ class AsmArrayFactory {
return methodNode; return methodNode;
} }
/* Creates the set method for ArraySetter classes */
private static MethodNode createSetMethodNode(Class<?> setterType, String arrayType) { private static MethodNode createSetMethodNode(Class<?> setterType, String arrayType) {
String elementType; String elementType;
if (setterType == ObjectArraySetter.class) { if (setterType == ObjectArraySetter.class) {
@ -99,7 +148,7 @@ class AsmArrayFactory {
switch (type) { switch (type) {
case "B": case "B":
case "Z": case "Z":
return new int[]{ILOAD, BASTORE}; return new int[]{ILOAD, BASTORE}; // load int, store byte??
case "S": case "S":
return new int[]{ILOAD, SASTORE}; return new int[]{ILOAD, SASTORE};
case "I": case "I":
@ -112,7 +161,6 @@ class AsmArrayFactory {
return new int[]{DLOAD, DASTORE}; return new int[]{DLOAD, DASTORE};
case "C": case "C":
return new int[]{ILOAD, CASTORE}; return new int[]{ILOAD, CASTORE};
default: default:
return new int[]{ALOAD, AASTORE}; return new int[]{ALOAD, AASTORE};
} }

View file

@ -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 ArraySetter {
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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