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.*;
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())) // we allow them and just filter out to allow
218                                 // static factory methods
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 }