Skip to content

Package: ReflectionUtils

ReflectionUtils

nameinstructionbranchcomplexitylinemethod
ReflectionUtils()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getClass(Type)
M: 4 C: 35
90%
M: 2 C: 6
75%
M: 2 C: 3
60%
M: 2 C: 10
83%
M: 0 C: 1
100%
getTypeArguments(Class, Class)
M: 49 C: 57
54%
M: 7 C: 7
50%
M: 5 C: 3
38%
M: 11 C: 12
52%
M: 0 C: 1
100%
hasInjectAnnotation(Field)
M: 3 C: 25
89%
M: 1 C: 3
75%
M: 1 C: 2
67%
M: 2 C: 7
78%
M: 0 C: 1
100%
injectDependencies(Object, Map)
M: 15 C: 39
72%
M: 2 C: 4
67%
M: 2 C: 2
50%
M: 3 C: 9
75%
M: 0 C: 1
100%
instantiateWithDependencies(Class, Map)
M: 13 C: 44
77%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 3 C: 6
67%
M: 0 C: 1
100%
static {...}
M: 0 C: 8
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%

Coverage

1: /*
2: * *************************************************************************************************************************************************************
3: *
4: * TheseFoolishThings: Miscellaneous utilities
5: * http://tidalwave.it/projects/thesefoolishthings
6: *
7: * Copyright (C) 2009 - 2024 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *************************************************************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12: * You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17: * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
18: *
19: * *************************************************************************************************************************************************************
20: *
21: * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
22: * git clone https://github.com/tidalwave-it/thesefoolishthings-src
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: * Adapted from <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=208860">this article</a>
51: *
52: * @author Ian Robertson
53: * @author Fabrizio Giudici
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: * Get the actual type arguments a subclass has used to extend a generic base class.
63: *
64: * @param <T> the static type of the base class
65: * @param baseClass the base class
66: * @param childClass the subclass
67: * @return a list of the raw classes for the actual type arguments.
68: **********************************************************************************************************************************************************/
69: @Nonnull
70: public static <T> List<Class<?>> getTypeArguments (@Nonnull final Class<T> baseClass,
71: @Nonnull final Class<? extends T> childClass)
72: {
73: final Map<Type, Type> resolvedTypes = new HashMap<>();
74: Type type = childClass;
75:
76: // start walking up the inheritance hierarchy until we hit baseClass
77:• while (!baseClass.equals(getClass(type)))
78: {
79:• if (type instanceof Class<?>)
80: {
81: // there is no useful information for us in raw types, so just keep going.
82: type = ((Class<?>)type).getGenericSuperclass();
83: }
84: else
85: {
86: final var parameterizedType = (ParameterizedType) type;
87: final var rawType = (Class<?>) parameterizedType.getRawType();
88: final var actualTypeArguments = parameterizedType.getActualTypeArguments();
89: final TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
90:
91:• for (var i = 0; i < actualTypeArguments.length; i++)
92: {
93: resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
94: }
95:
96:• if (!rawType.equals(baseClass))
97: {
98: type = rawType.getGenericSuperclass();
99: }
100: }
101: }
102:
103: // finally, for each actual type argument provided to baseClass, determine (if possible)
104: // the raw class for that type argument.
105: final Type[] actualTypeArguments;
106:
107:• if (type instanceof Class)
108: {
109: actualTypeArguments = ((Class<?>)type).getTypeParameters();
110: }
111: else
112: {
113: actualTypeArguments = ((ParameterizedType)type).getActualTypeArguments();
114: }
115:
116: final var typeArgumentsAsClasses = new ArrayList<Class<?>>();
117:
118: // resolve types by chasing down type variables.
119:• for (var baseType : actualTypeArguments)
120: {
121:• while (resolvedTypes.containsKey(baseType))
122: {
123: baseType = resolvedTypes.get(baseType);
124: }
125:
126: typeArgumentsAsClasses.add(getClass(baseType));
127: }
128:
129: return typeArgumentsAsClasses;
130: }
131:
132: /***********************************************************************************************************************************************************
133: * Instantiates an object of the given class performing dependency injections through the constructor.
134: *
135: * @param <T> the generic type of the object to instantiate
136: * @param type the dynamic type of the object to instantiate; it is expected to have a single constructor
137: * @param beans the bag of objects to instantiate
138: * @return the new instance
139: * @throws RuntimeException if something fails
140: * @since 3.2-ALPHA-17
141: **********************************************************************************************************************************************************/
142: public static <T> T instantiateWithDependencies (@Nonnull final Class<? extends T> type,
143: @Nonnull final Map<Class<?>, Object> beans)
144: {
145: try
146: {
147: log.debug("instantiateWithDependencies({}, {})", shortName(type), shortIds(beans.values()));
148: final var constructors = type.getConstructors();
149:
150:• if (constructors.length > 1)
151: {
152: throw new RuntimeException("Multiple constructors in " + type);
153: }
154:
155: final var parameters = Arrays.stream(constructors[0].getParameterTypes()).map(beans::get).collect(toList());
156:
157: log.trace(">>>> ctor arguments: {}", shortIds(parameters));
158: return type.cast(constructors[0].newInstance(parameters.toArray()));
159: }
160: catch (InstantiationException | IllegalAccessException | InvocationTargetException e)
161: {
162: throw new RuntimeException(e);
163: }
164: }
165:
166: /***********************************************************************************************************************************************************
167: * Performs dependency injection to an object by means of field introspection.
168: *
169: * @param object the object
170: * @param beans the bag of objects to instantiate
171: * @since 3.2-ALPHA-17
172: **********************************************************************************************************************************************************/
173: public static void injectDependencies (@Nonnull final Object object, @Nonnull final Map<Class<?>, Object> beans)
174: {
175:• for (final var field : object.getClass().getDeclaredFields())
176: {
177:• if (hasInjectAnnotation(field))
178: {
179: field.setAccessible(true);
180: final var type = field.getType();
181: final var dependency = beans.get(type);
182:
183:• if (dependency == null)
184: {
185: throw new RuntimeException("Can't inject " + object + "." + field.getName());
186: }
187:
188: try
189: {
190: field.set(object, dependency);
191: }
192: catch (IllegalArgumentException | IllegalAccessException e)
193: {
194: throw new RuntimeException(e);
195: }
196: }
197: }
198: }
199:
200: /***********************************************************************************************************************************************************
201: * Returns the class literal associated to the given type.
202: *
203: * @param type the type to inspect
204: * @return the class literal; it might be {@code null} if fails
205: **********************************************************************************************************************************************************/
206: @CheckForNull
207: public static Class<?> getClass (@Nonnull final Type type)
208: {
209: requireNonNull(type, "type");
210:
211:• if (type instanceof Class<?>)
212: {
213: return (Class<?>)type;
214: }
215:• else if (type instanceof ParameterizedType)
216: {
217: return getClass(((ParameterizedType)type).getRawType());
218: }
219:• else if (type instanceof GenericArrayType)
220: {
221: final var componentType = ((GenericArrayType)type).getGenericComponentType();
222: final var componentClass = getClass(componentType);
223:
224:• if (componentClass == null)
225: {
226: return null;
227: }
228:
229: return Array.newInstance(componentClass, 0).getClass();
230: }
231: else
232: {
233: // throw new IllegalArgumentException(type.toString());
234: return null;
235: }
236: }
237:
238: /***********************************************************************************************************************************************************
239: *
240: **********************************************************************************************************************************************************/
241: private static boolean hasInjectAnnotation (@Nonnull final Field field)
242: {
243: final var classLoader = Thread.currentThread().getContextClassLoader();
244:
245:• for (final var className : INJECT_CLASS_NAMES)
246: {
247: try
248: {
249: @SuppressWarnings("unchecked")
250: final var clazz = (Class<? extends Annotation>)classLoader.loadClass(className);
251:
252:• if (field.getAnnotation(clazz) != null)
253: {
254: return true;
255: }
256: }
257: catch (ClassNotFoundException ignored)
258: {
259: // try next
260: }
261: }
262:
263: return false;
264: }
265: }