Skip to content

Method: hasInjectAnnotation(Field)

1: /*
2: * *************************************************************************************************************************************************************
3: *
4: * TheseFoolishThings: Miscellaneous utilities
5: * http://tidalwave.it/projects/thesefoolishthings
6: *
7: * Copyright (C) 2009 - 2025 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. Note: if the base class is an interface, this method will work only
63: * if it is the first inherited interface in childClass.
64: *
65: * @param <T> the static type of the base class
66: * @param baseClass the base class
67: * @param childClass the subclass
68: * @return a list of the raw classes for the actual type arguments.
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: // start walking up the inheritance hierarchy until we hit baseClass
78: while (!baseClass.equals(getClass(type)))
79: {
80: if (type instanceof Class<?>)
81: {
82: if (baseClass.isInterface())
83: {
84: type = ((Class<?>)type).getGenericInterfaces()[0]; // FIXME: works only for one interface in hierarchy
85: }
86: else
87: {
88: type = ((Class<?>)type).getGenericSuperclass();
89: }
90: // there is no useful information for us in raw types, so just keep going.
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: // finally, for each actual type argument provided to baseClass, determine (if possible)
112: // the raw class for that type argument.
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: // resolve types by chasing down type variables.
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: * Instantiates an object of the given class performing dependency injections through the constructor.
142: *
143: * @param <T> the generic type of the object to instantiate
144: * @param type the dynamic type of the object to instantiate; it is expected to have a single constructor
145: * @param beans the bag of objects to instantiate
146: * @return the new instance
147: * @throws RuntimeException if something fails
148: * @since 3.2-ALPHA-17
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: * Performs dependency injection to an object by means of field introspection.
176: *
177: * @param object the object
178: * @param beans the bag of objects to instantiate
179: * @since 3.2-ALPHA-17
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: * Returns the class literal associated to the given type.
210: *
211: * @param type the type to inspect
212: * @return the class literal; it might be {@code null} if fails
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: // throw new IllegalArgumentException(type.toString());
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: // try next
268: }
269: }
270:
271: return false;
272: }
273: }