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