View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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())) // we allow them and just filter out to allow
234                                 // static factory methods
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 }