Skip to content

Commit 82fec65

Browse files
committed
Initial commit
0 parents  commit 82fec65

3 files changed

Lines changed: 248 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package dev.redio.genericUtils;
2+
3+
import dev.redio.genericUtils.exceptions.TypeResolutionException;
4+
import dev.redio.genericUtils.exceptions.TypeResolverInitException;
5+
6+
import java.lang.reflect.ParameterizedType;
7+
import java.lang.reflect.Type;
8+
import java.lang.reflect.TypeVariable;
9+
import java.util.ArrayList;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.function.Supplier;
14+
15+
/**
16+
* Tool to resolve the Class of a generic type parameter at runtime.
17+
*
18+
* @param <T> parameter to resolve.
19+
*/
20+
public abstract class GenericTypeResolver<T> implements Supplier<Class<T>> {
21+
22+
private static final Map<TypeVariable<?>, Class<?>> TYPE_CACHE = new HashMap<>();
23+
24+
private final Class<?> resolvedParameter;
25+
26+
protected GenericTypeResolver(final Class<?> provider) throws TypeResolutionException, TypeResolverInitException {
27+
final Class<?> thisClass = getClass();
28+
if (!thisClass.isAnonymousClass() || !thisClass.getSuperclass().equals(GenericTypeResolver.class))
29+
throw new TypeResolverInitException(thisClass + " is not a direct anonymous instance of GenericTypeResolver.");
30+
31+
final Type typeT = resolveGenericParameter();
32+
33+
if (typeT instanceof Class<?> classT) {
34+
resolvedParameter = classT;
35+
return;
36+
}
37+
38+
Class<?> classT = getClassFromCache(typeT);
39+
40+
if (classT != null) {
41+
resolvedParameter = classT;
42+
return;
43+
}
44+
45+
addTypeVariablesToCache(provider);
46+
47+
classT = getClassFromCache(classT);
48+
49+
if (classT != null) {
50+
resolvedParameter = classT;
51+
return;
52+
}
53+
54+
throw new TypeResolutionException(typeT);
55+
}
56+
57+
/**
58+
* Returns the result of the type resolution.
59+
*
60+
* @return the resolved type.
61+
*/
62+
@Override
63+
@SuppressWarnings("unchecked")
64+
public final Class<T> get() {
65+
return (Class<T>) resolvedParameter;
66+
}
67+
68+
@Override
69+
public final boolean equals(final Object o) {
70+
if (o == null)
71+
return false;
72+
73+
if (o instanceof GenericTypeResolver<?> resolver)
74+
return resolvedParameter.equals(resolver.resolvedParameter);
75+
return false;
76+
}
77+
78+
@Override
79+
public final int hashCode() {
80+
return resolvedParameter.hashCode();
81+
}
82+
83+
private Type resolveGenericParameter() throws TypeResolutionException {
84+
if (getClass().getGenericSuperclass() instanceof ParameterizedType parameterizedType)
85+
return parameterizedType.getActualTypeArguments()[0];
86+
throw new TypeResolutionException("Can't resolve parameter of non parameterized type.");
87+
}
88+
89+
private static Class<?> getClassFromCache(final Type typeT) {
90+
if (typeT instanceof TypeVariable<?> typeVariable)
91+
return TYPE_CACHE.get(typeVariable);
92+
return null;
93+
}
94+
95+
//
96+
// Matcher
97+
//
98+
99+
private static void addTypeVariablesToCache(Class<?> provider) {
100+
TYPE_CACHE.putAll(matchTypeParameterAndClass(
101+
getParameterizedSuperTypes(provider), getSuperTypeVariables(provider)));
102+
}
103+
104+
private static Map<TypeVariable<?>, Class<?>> matchTypeParameterAndClass(final List<ParameterizedType> container,
105+
final List<TypeVariable<?>> typeVariables) {
106+
final Map<TypeVariable<?>, Class<?>> result = new HashMap<>();
107+
108+
for (ParameterizedType type : container) {
109+
for (TypeVariable<?> variable : typeVariables) {
110+
final int index = getParameterTypeIndex(type, variable);
111+
if (index == -1)
112+
continue;
113+
result.put(variable, getParameterClassFromParameterizedType(type, index, container));
114+
}
115+
}
116+
return result;
117+
}
118+
119+
private static int getParameterTypeIndex(final ParameterizedType container, final TypeVariable<?> typeVariable) {
120+
if (container.getRawType() instanceof Class<?> rawClass) {
121+
final TypeVariable<?>[] containerTypeVariables = rawClass.getTypeParameters();
122+
for (int i = 0; i < containerTypeVariables.length; i++)
123+
if (containerTypeVariables[i] == typeVariable)
124+
return i;
125+
}
126+
127+
return -1;
128+
}
129+
130+
private static Class<?> getParameterClassFromParameterizedType(final ParameterizedType sourceType,
131+
final int index,
132+
final List<ParameterizedType> superTypes)
133+
throws IndexOutOfBoundsException, TypeResolutionException {
134+
final Type type = sourceType.getActualTypeArguments()[index];
135+
136+
if (type instanceof Class<?> clazz)
137+
return clazz;
138+
139+
if (type instanceof TypeVariable<?> typeVariable)
140+
return getParameterClassFromSuperTypes(typeVariable, superTypes);
141+
142+
if (type instanceof ParameterizedType parameterizedType
143+
&& parameterizedType.getRawType() instanceof Class<?> clazz)
144+
return clazz;
145+
146+
throw new TypeResolutionException(type);
147+
148+
}
149+
150+
private static Class<?> getParameterClassFromSuperTypes(final TypeVariable<?> typeVariable,
151+
final List<ParameterizedType> container) {
152+
for (ParameterizedType type : container) {
153+
final int index = getParameterTypeIndex(type, typeVariable);
154+
if (index == -1)
155+
continue;
156+
return getParameterClassFromParameterizedType(type, index, container);
157+
}
158+
159+
throw new TypeResolutionException(typeVariable);
160+
}
161+
162+
private static List<ParameterizedType> getParameterizedSuperTypes(final Class<?> provider) {
163+
final List<ParameterizedType> result = new ArrayList<>();
164+
165+
if (provider == null || Object.class.equals(provider))
166+
return result;
167+
168+
final Type genericSuperClass = provider.getGenericSuperclass();
169+
170+
if (genericSuperClass instanceof ParameterizedType parameterizedType)
171+
result.add(parameterizedType);
172+
173+
for (Type genericSuperInterface : provider.getGenericInterfaces())
174+
if (genericSuperInterface instanceof ParameterizedType parameterizedType)
175+
result.add(parameterizedType);
176+
177+
result.addAll(getParameterizedSuperTypes(provider.getSuperclass()));
178+
179+
for (Class<?> superInterface : provider.getInterfaces())
180+
result.addAll(getParameterizedSuperTypes(superInterface));
181+
182+
return result;
183+
}
184+
185+
private static List<TypeVariable<?>> getSuperTypeVariables(final Class<?> provider) {
186+
final List<TypeVariable<?>> result = new ArrayList<>();
187+
188+
if (provider == null || Object.class.equals(provider))
189+
return result;
190+
191+
result.addAll(List.of(provider.getTypeParameters()));
192+
193+
result.addAll(getSuperTypeVariables(provider.getSuperclass()));
194+
195+
for (Class<?> superInterface : provider.getInterfaces())
196+
result.addAll(getSuperTypeVariables(superInterface));
197+
198+
return result;
199+
}
200+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dev.redio.genericUtils.exceptions;
2+
3+
import java.lang.reflect.Type;
4+
5+
/**
6+
* Thrown to indicate that the resolution of a type failed.
7+
*/
8+
public class TypeResolutionException extends RuntimeException {
9+
public TypeResolutionException() {
10+
}
11+
12+
public TypeResolutionException(String message) {
13+
super(message);
14+
}
15+
16+
public TypeResolutionException(String message, Throwable cause) {
17+
super(message, cause);
18+
}
19+
20+
public TypeResolutionException(Throwable cause) {
21+
super(cause);
22+
}
23+
24+
public TypeResolutionException(Type type) {
25+
super("Type " + type + " couldn't be resolved to a class.");
26+
}
27+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dev.redio.genericUtils.exceptions;
2+
3+
/**
4+
* Thrown to indicate that the type resolver couldn't be initialized.
5+
*/
6+
public class TypeResolverInitException extends RuntimeException {
7+
public TypeResolverInitException() {
8+
}
9+
10+
public TypeResolverInitException(String message) {
11+
super(message);
12+
}
13+
14+
public TypeResolverInitException(String message, Throwable cause) {
15+
super(message, cause);
16+
}
17+
18+
public TypeResolverInitException(Throwable cause) {
19+
super(cause);
20+
}
21+
}

0 commit comments

Comments
 (0)