aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorAaron <51387595+AzureAaron@users.noreply.github.com>2024-04-06 04:57:46 -0400
committerGitHub <noreply@github.com>2024-04-06 04:57:46 -0400
commitb3623837bda701610868552005a25a5e5c148b8f (patch)
tree276c2edf10819583ef849e42f14b312bdc5ff375 /src/main/java
parenteadb318304b25594ea12466626a2c834da1d20cf (diff)
downloadSkyblocker-b3623837bda701610868552005a25a5e5c148b8f.tar.gz
Skyblocker-b3623837bda701610868552005a25a5e5c148b8f.tar.bz2
Skyblocker-b3623837bda701610868552005a25a5e5c148b8f.zip
Instanced Utils (#632)
* Instanced Utils * Use default equals and toString
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/InstancedUtils.java104
1 files changed, 104 insertions, 0 deletions
diff --git a/src/main/java/de/hysky/skyblocker/utils/InstancedUtils.java b/src/main/java/de/hysky/skyblocker/utils/InstancedUtils.java
new file mode 100644
index 00000000..1da4fa11
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/InstancedUtils.java
@@ -0,0 +1,104 @@
+package de.hysky.skyblocker.utils;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.runtime.ObjectMethods;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
+
+import org.slf4j.Logger;
+
+import com.mojang.logging.LogUtils;
+
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+
+/**
+ * @implNote If implementing any of these onto a class, ensure that all subclasses have an implementation of the methods too.
+ */
+public class InstancedUtils {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final Map<Class<?>, MethodHandle> EQUALS_CACHE = new ConcurrentHashMap<>();
+ private static final Map<Class<?>, MethodHandle> HASH_CODE_CACHE = new ConcurrentHashMap<>();
+ private static final Map<Class<?>, MethodHandle> TO_STRING_CACHE = new ConcurrentHashMap<>();
+
+ public static MethodHandle equals(Class<?> type) {
+ if (EQUALS_CACHE.containsKey(type)) return EQUALS_CACHE.get(type);
+
+ try {
+ Field[] fields = getClassFields(type);
+ MethodHandle[] getters = getFieldGetters(fields);
+
+ //The field names param can be anything as equals and hashCode don't care about it.
+ MethodHandle equalsHandle = (MethodHandle) ObjectMethods.bootstrap(MethodHandles.lookup(), "equals", MethodHandle.class, type, "", getters);
+
+ EQUALS_CACHE.put(type, equalsHandle);
+
+ return equalsHandle;
+ } catch (Throwable t) {
+ LOGGER.error("[Skyblocked Instanced Utils] Failed to create an equals method handle.", t);
+
+ throw new RuntimeException();
+ }
+ }
+
+ public static MethodHandle hashCode(Class<?> type) {
+ if (HASH_CODE_CACHE.containsKey(type)) return HASH_CODE_CACHE.get(type);
+
+ try {
+ Field[] fields = getClassFields(type);
+ MethodHandle[] getters = getFieldGetters(fields);
+
+ //The field names param can be anything as equals and hashCode don't care about it.
+ MethodHandle hashCodeHandle = (MethodHandle) ObjectMethods.bootstrap(MethodHandles.lookup(), "hashCode", MethodHandle.class, type, "", getters);
+
+ HASH_CODE_CACHE.put(type, hashCodeHandle);
+
+ return hashCodeHandle;
+ } catch (Throwable t) {
+ LOGGER.error("[Skyblocked Instanced Utils] Failed to create a hashCode method handle.", t);
+
+ throw new RuntimeException();
+ }
+ }
+
+ public static MethodHandle toString(Class<?> type) {
+ if (TO_STRING_CACHE.containsKey(type)) return TO_STRING_CACHE.get(type);
+
+ try {
+ Field[] fields = getClassFields(type);
+ MethodHandle[] getters = getFieldGetters(fields);
+ String fieldNames = String.join(";", Arrays.stream(fields).map(Field::getName).toArray(String[]::new));
+
+ MethodHandle toStringHandle = (MethodHandle) ObjectMethods.bootstrap(MethodHandles.lookup(), "toString", MethodHandle.class, type, fieldNames, getters);
+
+ TO_STRING_CACHE.put(type, toStringHandle);
+
+ return toStringHandle;
+ } catch (Throwable t) {
+ LOGGER.error("[Skyblocked Instanced Utils] Failed to create a toString method handle.", t);
+
+ throw new RuntimeException();
+ }
+ }
+
+ private static Field[] getClassFields(Class<?> type) {
+ return Stream.concat(Arrays.stream(type.getDeclaredFields()), Arrays.stream(type.getFields())).distinct().toArray(Field[]::new);
+ }
+
+ private static MethodHandle[] getFieldGetters(Field[] fields) throws Throwable {
+ ObjectOpenHashSet<MethodHandle> handles = new ObjectOpenHashSet<>();
+
+ for (Field field : fields) {
+ field.setAccessible(true);
+
+ MethodHandle getter = MethodHandles.lookup().unreflectGetter(field);
+
+ handles.add(getter);
+ }
+
+ return handles.toArray(MethodHandle[]::new);
+ }
+}