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                 .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())) // 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         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 }