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 package it.tidalwave.util;
27
28 import java.lang.annotation.Annotation;
29 import java.lang.reflect.Array;
30 import java.lang.reflect.Field;
31 import java.lang.reflect.GenericArrayType;
32 import java.lang.reflect.InvocationTargetException;
33 import java.lang.reflect.ParameterizedType;
34 import java.lang.reflect.Type;
35 import java.lang.reflect.TypeVariable;
36 import javax.annotation.CheckForNull;
37 import javax.annotation.Nonnull;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import lombok.extern.slf4j.Slf4j;
44 import static java.util.Objects.requireNonNull;
45 import static java.util.stream.Collectors.*;
46 import static it.tidalwave.util.ShortNames.*;
47
48
49
50
51
52
53
54
55
56 @Slf4j
57 public class ReflectionUtils
58 {
59 private static final List<String> INJECT_CLASS_NAMES = List.of("javax.inject.Inject", "jakarta.inject.Inject");
60
61
62
63
64
65
66
67
68
69
70 @Nonnull
71 public static <T> List<Class<?>> getTypeArguments (@Nonnull final Class<T> baseClass,
72 @Nonnull final Class<? extends T> childClass)
73 {
74 final Map<Type, Type> resolvedTypes = new HashMap<>();
75 Type type = childClass;
76
77
78 while (!baseClass.equals(getClass(type)))
79 {
80 if (type instanceof Class<?>)
81 {
82 if (baseClass.isInterface())
83 {
84 type = ((Class<?>)type).getGenericInterfaces()[0];
85 }
86 else
87 {
88 type = ((Class<?>)type).getGenericSuperclass();
89 }
90
91 }
92 else
93 {
94 final var parameterizedType = (ParameterizedType) type;
95 final var rawType = (Class<?>) parameterizedType.getRawType();
96 final var actualTypeArguments = parameterizedType.getActualTypeArguments();
97 final TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
98
99 for (var i = 0; i < actualTypeArguments.length; i++)
100 {
101 resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
102 }
103
104 if (!rawType.equals(baseClass))
105 {
106 type = rawType.getGenericSuperclass();
107 }
108 }
109 }
110
111
112
113 final Type[] actualTypeArguments;
114
115 if (type instanceof Class)
116 {
117 actualTypeArguments = ((Class<?>)type).getTypeParameters();
118 }
119 else
120 {
121 actualTypeArguments = ((ParameterizedType)type).getActualTypeArguments();
122 }
123
124 final var typeArgumentsAsClasses = new ArrayList<Class<?>>();
125
126
127 for (var baseType : actualTypeArguments)
128 {
129 while (resolvedTypes.containsKey(baseType))
130 {
131 baseType = resolvedTypes.get(baseType);
132 }
133
134 typeArgumentsAsClasses.add(getClass(baseType));
135 }
136
137 return typeArgumentsAsClasses;
138 }
139
140
141
142
143
144
145
146
147
148
149
150 public static <T> T instantiateWithDependencies (@Nonnull final Class<? extends T> type,
151 @Nonnull final Map<Class<?>, Object> beans)
152 {
153 try
154 {
155 log.debug("instantiateWithDependencies({}, {})", shortName(type), shortIds(beans.values()));
156 final var constructors = type.getConstructors();
157
158 if (constructors.length > 1)
159 {
160 throw new RuntimeException("Multiple constructors in " + type);
161 }
162
163 final var parameters = Arrays.stream(constructors[0].getParameterTypes()).map(beans::get).collect(toList());
164
165 log.trace(">>>> ctor arguments: {}", shortIds(parameters));
166 return type.cast(constructors[0].newInstance(parameters.toArray()));
167 }
168 catch (InstantiationException | IllegalAccessException | InvocationTargetException e)
169 {
170 throw new RuntimeException(e);
171 }
172 }
173
174
175
176
177
178
179
180
181 public static void injectDependencies (@Nonnull final Object object, @Nonnull final Map<Class<?>, Object> beans)
182 {
183 for (final var field : object.getClass().getDeclaredFields())
184 {
185 if (hasInjectAnnotation(field))
186 {
187 field.setAccessible(true);
188 final var type = field.getType();
189 final var dependency = beans.get(type);
190
191 if (dependency == null)
192 {
193 throw new RuntimeException("Can't inject " + object + "." + field.getName());
194 }
195
196 try
197 {
198 field.set(object, dependency);
199 }
200 catch (IllegalArgumentException | IllegalAccessException e)
201 {
202 throw new RuntimeException(e);
203 }
204 }
205 }
206 }
207
208
209
210
211
212
213
214 @CheckForNull
215 public static Class<?> getClass (@Nonnull final Type type)
216 {
217 requireNonNull(type, "type");
218
219 if (type instanceof Class<?>)
220 {
221 return (Class<?>)type;
222 }
223 else if (type instanceof ParameterizedType)
224 {
225 return getClass(((ParameterizedType)type).getRawType());
226 }
227 else if (type instanceof GenericArrayType)
228 {
229 final var componentType = ((GenericArrayType)type).getGenericComponentType();
230 final var componentClass = getClass(componentType);
231
232 if (componentClass == null)
233 {
234 return null;
235 }
236
237 return Array.newInstance(componentClass, 0).getClass();
238 }
239 else
240 {
241
242 return null;
243 }
244 }
245
246
247
248
249 private static boolean hasInjectAnnotation (@Nonnull final Field field)
250 {
251 final var classLoader = Thread.currentThread().getContextClassLoader();
252
253 for (final var className : INJECT_CLASS_NAMES)
254 {
255 try
256 {
257 @SuppressWarnings("unchecked")
258 final var clazz = (Class<? extends Annotation>)classLoader.loadClass(className);
259
260 if (field.getAnnotation(clazz) != null)
261 {
262 return true;
263 }
264 }
265 catch (ClassNotFoundException ignored)
266 {
267
268 }
269 }
270
271 return false;
272 }
273 }