aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/utils/InstancedUtils.java
blob: 1da4fa113ac7dc7109e232c2f6badf2b3e03f1b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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);
	}
}