diff --git a/src/main/java/com/github/shautvast/reflective/array/ArrayFactory.java b/src/main/java/com/github/shautvast/reflective/array/ArrayFactory.java index 0269b21..6763dff 100644 --- a/src/main/java/com/github/shautvast/reflective/array/ArrayFactory.java +++ b/src/main/java/com/github/shautvast/reflective/array/ArrayFactory.java @@ -1,18 +1,12 @@ package com.github.shautvast.reflective.array; 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 { - private static final Map creatorCache = new ConcurrentHashMap<>(); - private static final Map, Map> setterCache = new ConcurrentHashMap<>(); /** * 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 */ 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 + * * @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); + ArrayHandlerFactory.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); + ArrayHandlerFactory.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); + ArrayHandlerFactory.getSetterInstance(ByteArraySetter.class, typeChecked(array)).set(array, index, value); } /** @@ -63,7 +60,7 @@ public class ArrayFactory { * @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); + ArrayHandlerFactory.getSetterInstance(ShortArraySetter.class, typeChecked(array)).set(array, index, value); } /** @@ -74,7 +71,7 @@ public class ArrayFactory { * @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); + ArrayHandlerFactory.getSetterInstance(LongArraySetter.class, typeChecked(array)).set(array, index, value); } /** @@ -85,7 +82,7 @@ public class ArrayFactory { * @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); + ArrayHandlerFactory.getSetterInstance(FloatArraySetter.class, typeChecked(array)).set(array, index, value); } /** @@ -96,17 +93,20 @@ public class ArrayFactory { * @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); + ArrayHandlerFactory.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); + ArrayHandlerFactory.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); + 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) { Class arrayType = array.getClass(); if (!arrayType.isArray()) { @@ -114,34 +114,4 @@ public class ArrayFactory { } return arrayType; } - - @SuppressWarnings("unchecked") - private static T getSetterInstance(Class 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(); - } } diff --git a/src/main/java/com/github/shautvast/reflective/array/AsmArrayFactory.java b/src/main/java/com/github/shautvast/reflective/array/ArrayHandlerFactory.java similarity index 64% rename from src/main/java/com/github/shautvast/reflective/array/AsmArrayFactory.java rename to src/main/java/com/github/shautvast/reflective/array/ArrayHandlerFactory.java index bc0c623..1ca274a 100644 --- a/src/main/java/com/github/shautvast/reflective/array/AsmArrayFactory.java +++ b/src/main/java/com/github/shautvast/reflective/array/ArrayHandlerFactory.java @@ -1,6 +1,7 @@ package com.github.shautvast.reflective.array; 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.java.ASM; import com.github.shautvast.reflective.java.ByteClassLoader; @@ -11,10 +12,55 @@ 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 AsmArrayFactory { +class ArrayHandlerFactory { + /* cache for the compiled creator classes */ + private static final Map 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, Map> setterCache = new ConcurrentHashMap<>(); + + + /* + * generic method for creating array setters (primitives and objects) + */ + @SuppressWarnings("unchecked") + static T getSetterInstance(Class 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) { ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(ArrayCreator.class)); classNode.methods.add(createNewInstanceMethodNode(elementType, dimensions)); @@ -38,6 +84,7 @@ class AsmArrayFactory { } } + /* Creates the ASM ClassNode for an ArraySetter */ static T createSyntheticArraySetter(Class setterType, String arrayType, String name) { ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(setterType)); 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) { MethodNode methodNode = new MethodNode(ACC_PUBLIC, "newInstance", "()Ljava/lang/Object;", null, null); @@ -71,6 +119,7 @@ class AsmArrayFactory { return methodNode; } + /* Creates the set method for ArraySetter classes */ private static MethodNode createSetMethodNode(Class setterType, String arrayType) { String elementType; if (setterType == ObjectArraySetter.class) { @@ -99,7 +148,7 @@ class AsmArrayFactory { switch (type) { case "B": case "Z": - return new int[]{ILOAD, BASTORE}; + return new int[]{ILOAD, BASTORE}; // load int, store byte?? case "S": return new int[]{ILOAD, SASTORE}; case "I": @@ -112,7 +161,6 @@ class AsmArrayFactory { return new int[]{DLOAD, DASTORE}; case "C": return new int[]{ILOAD, CASTORE}; - default: return new int[]{ALOAD, AASTORE}; } diff --git a/src/main/java/com/github/shautvast/reflective/array/base/ArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/ArraySetter.java new file mode 100644 index 0000000..ca4ae27 --- /dev/null +++ b/src/main/java/com/github/shautvast/reflective/array/base/ArraySetter.java @@ -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 { +} diff --git a/src/main/java/com/github/shautvast/reflective/array/base/BooleanArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/BooleanArraySetter.java index eaf9b2b..fef0909 100644 --- a/src/main/java/com/github/shautvast/reflective/array/base/BooleanArraySetter.java +++ b/src/main/java/com/github/shautvast/reflective/array/base/BooleanArraySetter.java @@ -1,6 +1,5 @@ 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); } diff --git a/src/main/java/com/github/shautvast/reflective/array/base/ByteArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/ByteArraySetter.java index 3e9b074..06796b0 100644 --- a/src/main/java/com/github/shautvast/reflective/array/base/ByteArraySetter.java +++ b/src/main/java/com/github/shautvast/reflective/array/base/ByteArraySetter.java @@ -1,6 +1,6 @@ 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); } diff --git a/src/main/java/com/github/shautvast/reflective/array/base/CharArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/CharArraySetter.java index dc090b7..8453275 100644 --- a/src/main/java/com/github/shautvast/reflective/array/base/CharArraySetter.java +++ b/src/main/java/com/github/shautvast/reflective/array/base/CharArraySetter.java @@ -1,6 +1,6 @@ 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); } diff --git a/src/main/java/com/github/shautvast/reflective/array/base/DoubleArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/DoubleArraySetter.java index 826a4bb..a4b1275 100644 --- a/src/main/java/com/github/shautvast/reflective/array/base/DoubleArraySetter.java +++ b/src/main/java/com/github/shautvast/reflective/array/base/DoubleArraySetter.java @@ -1,6 +1,6 @@ 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); } diff --git a/src/main/java/com/github/shautvast/reflective/array/base/FloatArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/FloatArraySetter.java index 5a67788..f279048 100644 --- a/src/main/java/com/github/shautvast/reflective/array/base/FloatArraySetter.java +++ b/src/main/java/com/github/shautvast/reflective/array/base/FloatArraySetter.java @@ -1,6 +1,6 @@ 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); } diff --git a/src/main/java/com/github/shautvast/reflective/array/base/IntArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/IntArraySetter.java index 240fd86..c04d0a9 100644 --- a/src/main/java/com/github/shautvast/reflective/array/base/IntArraySetter.java +++ b/src/main/java/com/github/shautvast/reflective/array/base/IntArraySetter.java @@ -1,5 +1,5 @@ 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); } diff --git a/src/main/java/com/github/shautvast/reflective/array/base/LongArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/LongArraySetter.java index 9ab95f4..ecf8309 100644 --- a/src/main/java/com/github/shautvast/reflective/array/base/LongArraySetter.java +++ b/src/main/java/com/github/shautvast/reflective/array/base/LongArraySetter.java @@ -1,6 +1,6 @@ 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); } diff --git a/src/main/java/com/github/shautvast/reflective/array/base/ObjectArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/ObjectArraySetter.java index 4616dbe..f7f71fb 100644 --- a/src/main/java/com/github/shautvast/reflective/array/base/ObjectArraySetter.java +++ b/src/main/java/com/github/shautvast/reflective/array/base/ObjectArraySetter.java @@ -1,5 +1,5 @@ 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); } diff --git a/src/main/java/com/github/shautvast/reflective/array/base/ShortArraySetter.java b/src/main/java/com/github/shautvast/reflective/array/base/ShortArraySetter.java index 8780e98..3be5d80 100644 --- a/src/main/java/com/github/shautvast/reflective/array/base/ShortArraySetter.java +++ b/src/main/java/com/github/shautvast/reflective/array/base/ShortArraySetter.java @@ -1,6 +1,6 @@ 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); }