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 .collect(toList());
175
176 List<Method> factoryMethods = Arrays.stream(cls.getDeclaredMethods())
177 .filter(method -> method.getReturnType() == cls && Modifier.isStatic(method.getModifiers()))
178 .collect(toList());
179 List<Method> injectFactoryMethods = factoryMethods.stream()
180 .filter(method -> method.isAnnotationPresent(Inject.class))
181 .collect(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 return new BindingInitializer<T>(Collections.singleton(key)) {
244 @Override
245 public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
246 Supplier<?> binding = compiler.apply(key);
247 return (T instance) -> {
248 Object arg = binding.get();
249 try {
250 field.set(instance, arg);
251 } catch (IllegalAccessException e) {
252 throw new DIException("Not allowed to set injectable field " + field, e);
253 }
254 };
255 }
256 };
257 }
258
259 public static <T> BindingInitializer<T> methodInjector(Key<T> container, Method method) {
260 method.setAccessible(true);
261 Key<?>[] dependencies = toDependencies(container.getType(), method);
262 return new BindingInitializer<T>(new HashSet<>(Arrays.asList(dependencies))) {
263 @Override
264 public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
265 return instance -> {
266 Object[] args = getDependencies().stream()
267 .map(compiler)
268 .map(Supplier::get)
269 .toArray();
270 try {
271 method.invoke(instance, args);
272 } catch (IllegalAccessException e) {
273 throw new DIException("Not allowed to call injectable method " + method, e);
274 } catch (InvocationTargetException e) {
275 throw new DIException("Failed to call injectable method " + method, e.getCause());
276 }
277 };
278 }
279 };
280 }
281
282 public static Key<?>[] toDependencies(@Nullable Type container, Executable executable) {
283 Key<?>[] keys = toArgDependencies(container, executable);
284 if (executable instanceof Constructor || Modifier.isStatic(executable.getModifiers())) {
285 return keys;
286 } else {
287 Key<?>[] nkeys = new Key[keys.length + 1];
288 nkeys[0] = Key.ofType(container);
289 System.arraycopy(keys, 0, nkeys, 1, keys.length);
290 return nkeys;
291 }
292 }
293
294 private static Key<?>[] toArgDependencies(@Nullable Type container, Executable executable) {
295 Parameter[] parameters = executable.getParameters();
296 Key<?>[] dependencies = new Key<?>[parameters.length];
297 if (parameters.length == 0) {
298 return dependencies;
299 }
300
301 Type type = parameters[0].getParameterizedType();
302 Parameter parameter = parameters[0];
303 dependencies[0] = keyOf(container, type, parameter);
304
305 Type[] genericParameterTypes = executable.getGenericParameterTypes();
306 boolean hasImplicitDependency = genericParameterTypes.length != parameters.length;
307 for (int i = 1; i < dependencies.length; i++) {
308 type = genericParameterTypes[hasImplicitDependency ? i - 1 : i];
309 parameter = parameters[i];
310 dependencies[i] = keyOf(container, type, parameter);
311 }
312 return dependencies;
313 }
314
315 @SuppressWarnings("unchecked")
316 public static <T> Binding<T> bindingFromMethod(Method method) {
317 method.setAccessible(true);
318 Binding<T> binding = Binding.to(
319 Key.ofType(method.getGenericReturnType(), ReflectionUtils.qualifierOf(method)),
320 args -> {
321 try {
322 Object instance;
323 Object[] params;
324 if (Modifier.isStatic(method.getModifiers())) {
325 instance = null;
326 params = args;
327 } else {
328 instance = args[0];
329 params = Arrays.copyOfRange(args, 1, args.length);
330 }
331 T result = (T) method.invoke(instance, params);
332 if (result == null) {
333 throw new NullPointerException(
334 "@Provides method must return non-null result, method " + method);
335 }
336 return result;
337 } catch (IllegalAccessException e) {
338 throw new DIException("Not allowed to call method " + method, e);
339 } catch (InvocationTargetException e) {
340 throw new DIException("Failed to call method " + method, e.getCause());
341 }
342 },
343 toDependencies(method.getDeclaringClass(), method));
344
345 Priority priority = method.getAnnotation(Priority.class);
346 if (priority != null) {
347 binding = binding.prioritize(priority.value());
348 }
349
350 return binding;
351 }
352
353 public static <T> Binding<T> bindingFromConstructor(Key<T> key, Constructor<T> constructor) {
354 constructor.setAccessible(true);
355
356 Key<?>[] dependencies = toDependencies(key.getType(), constructor);
357
358 Binding<T> binding = Binding.to(
359 key,
360 args -> {
361 try {
362 return constructor.newInstance(args);
363 } catch (InstantiationException e) {
364 throw new DIException(
365 "Cannot instantiate object from the constructor " + constructor
366 + " to provide requested key " + key,
367 e);
368 } catch (IllegalAccessException e) {
369 throw new DIException(
370 "Not allowed to call constructor " + constructor + " to provide requested key " + key,
371 e);
372 } catch (InvocationTargetException e) {
373 throw new DIException(
374 "Failed to call constructor " + constructor + " to provide requested key " + key,
375 e.getCause());
376 }
377 },
378 dependencies);
379
380 Priority priority = constructor.getDeclaringClass().getAnnotation(Priority.class);
381 if (priority != null) {
382 binding = binding.prioritize(priority.value());
383 }
384
385 return binding.withKey(key);
386 }
387 }