1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.di.impl;
20
21 import java.lang.annotation.Annotation;
22 import java.lang.reflect.AnnotatedElement;
23 import java.lang.reflect.Constructor;
24 import java.lang.reflect.Executable;
25 import java.lang.reflect.Field;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Member;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30 import java.lang.reflect.Parameter;
31 import java.lang.reflect.Type;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.function.Consumer;
38 import java.util.function.Function;
39 import java.util.function.Supplier;
40 import java.util.regex.Pattern;
41 import java.util.stream.Stream;
42
43 import org.apache.maven.api.annotations.Nullable;
44 import org.apache.maven.api.di.Inject;
45 import org.apache.maven.api.di.Named;
46 import org.apache.maven.api.di.Priority;
47 import org.apache.maven.api.di.Qualifier;
48 import org.apache.maven.di.Key;
49
50 import static java.util.stream.Collectors.toList;
51
52 public final class ReflectionUtils {
53 private static final String IDENT = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
54
55 private static final Pattern PACKAGE = Pattern.compile("(?:" + IDENT + "\\.)*");
56 private static final Pattern PACKAGE_AND_PARENT = Pattern.compile(PACKAGE.pattern() + "(?:" + IDENT + "\\$\\d*)?");
57 private static final Pattern ARRAY_SIGNATURE = Pattern.compile("\\[L(.*?);");
58
59 public static String getDisplayName(Type type) {
60 Class<?> raw = Types.getRawType(type);
61 String typeName;
62 if (raw.isAnonymousClass()) {
63 Type superclass = raw.getGenericSuperclass();
64 typeName = "? extends " + superclass.getTypeName();
65 } else {
66 typeName = type.getTypeName();
67 }
68
69 return PACKAGE_AND_PARENT
70 .matcher(ARRAY_SIGNATURE.matcher(typeName).replaceAll("$1[]"))
71 .replaceAll("");
72 }
73
74 public static @Nullable Object getOuterClassInstance(Object innerClassInstance) {
75 if (innerClassInstance == null) {
76 return null;
77 }
78 Class<?> cls = innerClassInstance.getClass();
79 Class<?> enclosingClass = cls.getEnclosingClass();
80 if (enclosingClass == null) {
81 return null;
82 }
83 for (Field field : cls.getDeclaredFields()) {
84 if (!field.isSynthetic() || !field.getName().startsWith("this$") || field.getType() != enclosingClass) {
85 continue;
86 }
87 field.setAccessible(true);
88 try {
89 return field.get(innerClassInstance);
90 } catch (IllegalAccessException e) {
91 throw new RuntimeException(e);
92 }
93 }
94 return null;
95 }
96
97 public static @Nullable Object qualifierOf(AnnotatedElement annotatedElement) {
98 Object qualifier = null;
99 for (Annotation annotation : annotatedElement.getDeclaredAnnotations()) {
100 if (annotation.annotationType().isAnnotationPresent(Qualifier.class)) {
101 if (qualifier != null) {
102 throw new DIException("More than one qualifier annotation on " + annotatedElement);
103 }
104 if (annotation instanceof Named) {
105 qualifier = ((Named) annotation).value();
106 } else {
107 Class<? extends Annotation> annotationType = annotation.annotationType();
108 qualifier = Utils.isMarker(annotationType) ? annotationType : annotation;
109 }
110 }
111 }
112 return qualifier;
113 }
114
115 public static @Nullable Annotation scopeOf(AnnotatedElement annotatedElement) {
116 Annotation scope = null;
117 for (Annotation annotation : annotatedElement.getDeclaredAnnotations()) {
118 if (annotation.annotationType().isAnnotationPresent(org.apache.maven.api.di.Scope.class)) {
119 if (scope != null) {
120 throw new DIException("More than one scope annotation on " + annotatedElement);
121 }
122 scope = annotation;
123 }
124 }
125 return scope;
126 }
127
128 public static <T> Key<T> keyOf(@Nullable Type container, Type type, AnnotatedElement annotatedElement) {
129 return Key.ofType(
130 container != null ? Types.bind(type, Types.getAllTypeBindings(container)) : type,
131 qualifierOf(annotatedElement));
132 }
133
134 public static <T extends AnnotatedElement & Member> List<T> getAnnotatedElements(
135 Class<?> cls,
136 Class<? extends Annotation> annotationType,
137 Function<Class<?>, T[]> extractor,
138 boolean allowStatic) {
139 List<T> result = new ArrayList<>();
140 while (cls != null) {
141 for (T element : extractor.apply(cls)) {
142 if (element.isAnnotationPresent(annotationType)) {
143 if (!allowStatic && Modifier.isStatic(element.getModifiers())) {
144 throw new DIException(
145 "@" + annotationType.getSimpleName() + " annotation is not allowed on " + element);
146 }
147 result.add(element);
148 }
149 }
150 cls = cls.getSuperclass();
151 }
152 return result;
153 }
154
155 public static <T> @Nullable Binding<T> generateImplicitBinding(Key<T> key) {
156 Binding<T> binding = generateConstructorBinding(key);
157 if (binding != null) {
158 Annotation scope = scopeOf(key.getRawType());
159 if (scope != null) {
160 binding = binding.scope(scope);
161 }
162 binding = binding.initializeWith(generateInjectingInitializer(key));
163 }
164 return binding;
165 }
166
167 @SuppressWarnings("unchecked")
168 public static <T> @Nullable Binding<T> generateConstructorBinding(Key<T> key) {
169 Class<?> cls = key.getRawType();
170
171 List<Constructor<?>> constructors = Arrays.asList(cls.getDeclaredConstructors());
172 List<Constructor<?>> injectConstructors = constructors.stream()
173 .filter(c -> c.isAnnotationPresent(Inject.class))
174 .toList();
175
176 List<Method> factoryMethods = Arrays.stream(cls.getDeclaredMethods())
177 .filter(method -> method.getReturnType() == cls && Modifier.isStatic(method.getModifiers()))
178 .toList();
179 List<Method> injectFactoryMethods = factoryMethods.stream()
180 .filter(method -> method.isAnnotationPresent(Inject.class))
181 .toList();
182
183 if (!injectConstructors.isEmpty()) {
184 if (injectConstructors.size() > 1) {
185 throw failedImplicitBinding(key, "more than one inject constructor");
186 }
187 if (!injectFactoryMethods.isEmpty()) {
188 throw failedImplicitBinding(key, "both inject constructor and inject factory method are present");
189 }
190 return bindingFromConstructor(
191 key, (Constructor<T>) injectConstructors.iterator().next());
192 }
193
194 if (!injectFactoryMethods.isEmpty()) {
195 if (injectFactoryMethods.size() > 1) {
196 throw failedImplicitBinding(key, "more than one inject factory method");
197 }
198 return bindingFromMethod(injectFactoryMethods.iterator().next());
199 }
200
201 if (constructors.isEmpty()) {
202 throw failedImplicitBinding(key, "inject annotation on interface");
203 }
204 if (constructors.size() > 1) {
205 throw failedImplicitBinding(key, "inject annotation on class with multiple constructors");
206 }
207 Constructor<T> declaredConstructor =
208 (Constructor<T>) constructors.iterator().next();
209
210 Class<?> enclosingClass = cls.getEnclosingClass();
211 if (enclosingClass != null
212 && !Modifier.isStatic(cls.getModifiers())
213 && declaredConstructor.getParameterCount() != 1) {
214 throw failedImplicitBinding(
215 key,
216 "inject annotation on local class that closes over outside variables and/or has no default constructor");
217 }
218 return bindingFromConstructor(key, declaredConstructor);
219 }
220
221 private static DIException failedImplicitBinding(Key<?> requestedKey, String message) {
222 return new DIException(
223 "Failed to generate implicit binding for " + requestedKey.getDisplayString() + ", " + message);
224 }
225
226 public static <T> BindingInitializer<T> generateInjectingInitializer(Key<T> container) {
227 Class<T> rawType = container.getRawType();
228 List<BindingInitializer<T>> initializers = Stream.concat(
229 getAnnotatedElements(rawType, Inject.class, Class::getDeclaredFields, false).stream()
230 .map(field -> fieldInjector(container, field)),
231 getAnnotatedElements(rawType, Inject.class, Class::getDeclaredMethods, true).stream()
232 .filter(method -> !Modifier.isStatic(
233 method.getModifiers()))
234
235 .map(method -> methodInjector(container, method)))
236 .collect(toList());
237 return BindingInitializer.combine(initializers);
238 }
239
240 public static <T> BindingInitializer<T> fieldInjector(Key<T> container, Field field) {
241 field.setAccessible(true);
242 Key<Object> key = keyOf(container.getType(), field.getGenericType(), field);
243 boolean optional = field.isAnnotationPresent(Nullable.class);
244 Dependency<Object> dep = new Dependency<>(key, optional);
245 return new BindingInitializer<T>(Collections.singleton(dep)) {
246 @Override
247 public Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
248 Supplier<?> binding = compiler.apply(dep);
249 return (T instance) -> {
250 Object arg = binding.get();
251 try {
252 field.set(instance, arg);
253 } catch (IllegalAccessException e) {
254 throw new DIException("Not allowed to set injectable field " + field, e);
255 }
256 };
257 }
258 };
259 }
260
261 public static <T> BindingInitializer<T> methodInjector(Key<T> container, Method method) {
262 method.setAccessible(true);
263 Dependency<?>[] dependencies = toDependencies(container.getType(), method);
264 return new BindingInitializer<T>(new HashSet<>(Arrays.asList(dependencies))) {
265 @Override
266 public Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
267 return instance -> {
268 Object[] args = getDependencies().stream()
269 .map(compiler)
270 .map(Supplier::get)
271 .toArray();
272 try {
273 method.invoke(instance, args);
274 } catch (IllegalAccessException e) {
275 throw new DIException("Not allowed to call injectable method " + method, e);
276 } catch (InvocationTargetException e) {
277 throw new DIException("Failed to call injectable method " + method, e.getCause());
278 }
279 };
280 }
281 };
282 }
283
284 public static Dependency<?>[] toDependencies(@Nullable Type container, Executable executable) {
285 Dependency<?>[] keys = toArgDependencies(container, executable);
286 if (executable instanceof Constructor || Modifier.isStatic(executable.getModifiers())) {
287 return keys;
288 } else {
289 Dependency<?>[] nkeys = new Dependency[keys.length + 1];
290 nkeys[0] = new Dependency<>(Key.ofType(container), false);
291 System.arraycopy(keys, 0, nkeys, 1, keys.length);
292 return nkeys;
293 }
294 }
295
296 private static Dependency<?>[] toArgDependencies(@Nullable Type container, Executable executable) {
297 Parameter[] parameters = executable.getParameters();
298 Dependency<?>[] dependencies = new Dependency<?>[parameters.length];
299 if (parameters.length == 0) {
300 return dependencies;
301 }
302
303 Type[] genericParameterTypes = executable.getGenericParameterTypes();
304 for (int i = 0; i < dependencies.length; i++) {
305 Type type = genericParameterTypes[i];
306 Parameter parameter = parameters[i];
307 boolean optional = parameter.isAnnotationPresent(Nullable.class);
308 dependencies[i] = new Dependency<>(keyOf(container, type, parameter), optional);
309 }
310 return dependencies;
311 }
312
313 @SuppressWarnings("unchecked")
314 public static <T> Binding<T> bindingFromMethod(Method method) {
315 method.setAccessible(true);
316 Binding<T> binding = Binding.to(
317 Key.ofType(method.getGenericReturnType(), ReflectionUtils.qualifierOf(method)),
318 args -> {
319 try {
320 Object instance;
321 Object[] params;
322 if (Modifier.isStatic(method.getModifiers())) {
323 instance = null;
324 params = args;
325 } else {
326 instance = args[0];
327 params = Arrays.copyOfRange(args, 1, args.length);
328 }
329 T result = (T) method.invoke(instance, params);
330 if (result == null) {
331 throw new NullPointerException(
332 "@Provides method must return non-null result, method " + method);
333 }
334 return result;
335 } catch (IllegalAccessException e) {
336 throw new DIException("Not allowed to call method " + method, e);
337 } catch (InvocationTargetException e) {
338 throw new DIException("Failed to call method " + method, e.getCause());
339 }
340 },
341 toDependencies(method.getDeclaringClass(), method));
342
343 Priority priority = method.getAnnotation(Priority.class);
344 if (priority != null) {
345 binding = binding.prioritize(priority.value());
346 }
347
348 return binding;
349 }
350
351 public static <T> Binding<T> bindingFromConstructor(Key<T> key, Constructor<T> constructor) {
352 constructor.setAccessible(true);
353
354 Dependency<?>[] dependencies = toDependencies(key.getType(), constructor);
355
356 Binding<T> binding = Binding.to(
357 key,
358 args -> {
359 try {
360 return constructor.newInstance(args);
361 } catch (InstantiationException e) {
362 throw new DIException(
363 "Cannot instantiate object from the constructor " + constructor
364 + " to provide requested key " + key,
365 e);
366 } catch (IllegalAccessException e) {
367 throw new DIException(
368 "Not allowed to call constructor " + constructor + " to provide requested key " + key,
369 e);
370 } catch (InvocationTargetException e) {
371 throw new DIException(
372 "Failed to call constructor " + constructor + " to provide requested key " + key,
373 e.getCause());
374 }
375 },
376 dependencies);
377
378 Priority priority = constructor.getDeclaringClass().getAnnotation(Priority.class);
379 if (priority != null) {
380 binding = binding.prioritize(priority.value());
381 }
382
383 return binding.withKey(key);
384 }
385 }