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