• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.inject.mini;
17 
18 import java.lang.annotation.Annotation;
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Member;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.ParameterizedType;
25 import java.lang.reflect.Type;
26 import java.util.ArrayDeque;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Queue;
34 import java.util.Set;
35 import javax.inject.Provider;
36 
37 /**
38  * Proof of concept. A tiny injector suitable for tiny applications.
39  *
40  * @author jessewilson@google.com (Jesse Wilson)
41  * @since 3.0
42  */
43 public final class MiniGuice {
44   private static final Object UNINITIALIZED = new Object();
45 
MiniGuice()46   private MiniGuice() {}
47 
48   private final Map<Key, Provider<?>> bindings = new HashMap<>();
49   private final Queue<RequiredKey> requiredKeys = new ArrayDeque<>();
50   private final Set<Key> singletons = new HashSet<>();
51 
52   /**
53    * Creates an injector defined by {@code modules} and immediately uses it to create an instance of
54    * {@code type}. The modules can be of any type, and must contain {@code @Provides} methods.
55    *
56    * <p>The following injection features are supported:
57    *
58    * <ul>
59    * <li>Field injection. A class may have any number of field injections, and fields may be of any
60    *     visibility. Static fields will be injected each time an instance is injected.
61    * <li>Constructor injection. A class may have a single {@code @Inject}-annotated constructor.
62    *     Classes that have fields injected may omit the {@link @Inject} annotation if they have a
63    *     public no-arguments constructor.
64    * <li>Injection of {@code @Provides} method parameters.
65    * <li>{@code @Provides} methods annotated {@code @Singleton}.
66    * <li>Constructor-injected classes annotated {@code @Singleton}.
67    * <li>Injection of {@link Provider}s.
68    * <li>Binding annotations on injected parameters and fields.
69    * <li>Guice annotations.
70    * <li>JSR 330 annotations.
71    * <li>Eager loading of singletons.
72    * </ul>
73    *
74    * <p><strong>Note that method injection is not supported.</strong>
75    */
inject(Class<T> type, Object... modules)76   public static <T> T inject(Class<T> type, Object... modules) {
77     Key key = new Key(type, null);
78     MiniGuice miniGuice = new MiniGuice();
79     for (Object module : modules) {
80       miniGuice.install(module);
81     }
82     miniGuice.requireKey(key, "root injection");
83     miniGuice.addJitBindings();
84     miniGuice.addProviderBindings();
85     miniGuice.eagerlyLoadSingletons();
86     Provider<?> provider = miniGuice.bindings.get(key);
87     return type.cast(provider.get());
88   }
89 
addProviderBindings()90   private void addProviderBindings() {
91     Map<Key, Provider<?>> providerBindings = new HashMap<>();
92     for (final Map.Entry<Key, Provider<?>> binding : bindings.entrySet()) {
93       Key key = binding.getKey();
94       final Provider<?> value = binding.getValue();
95       Provider<Provider<?>> providerProvider =
96           new Provider<Provider<?>>() {
97             @Override
98             public Provider<?> get() {
99               return value;
100             }
101           };
102       providerBindings.put(
103           new Key(new ProviderType(javax.inject.Provider.class, key.type), key.annotation),
104           providerProvider);
105     }
106     bindings.putAll(providerBindings);
107   }
108 
requireKey(Key key, Object requiredBy)109   private void requireKey(Key key, Object requiredBy) {
110     if (key.type instanceof ParameterizedType
111         && (((ParameterizedType) key.type).getRawType() == Provider.class
112             || ((ParameterizedType) key.type).getRawType() == javax.inject.Provider.class)) {
113       Type type = ((ParameterizedType) key.type).getActualTypeArguments()[0];
114       key = new Key(type, key.annotation);
115     }
116 
117     requiredKeys.add(new RequiredKey(key, requiredBy));
118   }
119 
eagerlyLoadSingletons()120   private void eagerlyLoadSingletons() {
121     for (Key key : singletons) {
122       Provider<?> provider = bindings.get(key);
123       final Object onlyInstance = provider.get();
124       bindings.put(
125           key,
126           new Provider<Object>() {
127             @Override
128             public Object get() {
129               return onlyInstance;
130             }
131           });
132     }
133   }
134 
install(Object module)135   public void install(Object module) {
136     boolean hasProvidesMethods = false;
137     for (Class<?> c = module.getClass(); c != Object.class; c = c.getSuperclass()) {
138       for (Method method : c.getDeclaredMethods()) {
139         if (method.isAnnotationPresent(com.google.inject.Provides.class)) {
140           Key key = key(method, method.getGenericReturnType(), method.getAnnotations());
141           addProviderMethodBinding(key, module, method);
142           hasProvidesMethods = true;
143         }
144       }
145     }
146     if (!hasProvidesMethods) {
147       throw new IllegalArgumentException("No @Provides methods on " + module);
148     }
149   }
150 
addProviderMethodBinding(Key key, final Object instance, final Method method)151   private void addProviderMethodBinding(Key key, final Object instance, final Method method) {
152     final Key[] parameterKeys =
153         parametersToKeys(
154             method, method.getGenericParameterTypes(), method.getParameterAnnotations());
155     method.setAccessible(true);
156     final Provider<Object> unscoped =
157         new Provider<Object>() {
158           @Override
159           public Object get() {
160             Object[] parameters = keysToValues(parameterKeys);
161             try {
162               return method.invoke(instance, parameters);
163             } catch (IllegalAccessException e) {
164               throw new RuntimeException(e);
165             } catch (InvocationTargetException e) {
166               throw new RuntimeException(e.getCause());
167             }
168           }
169         };
170 
171     boolean singleton = method.isAnnotationPresent(javax.inject.Singleton.class);
172     putBinding(key, unscoped, singleton);
173   }
174 
addJitBindings()175   private void addJitBindings() {
176     RequiredKey requiredKey;
177     while ((requiredKey = requiredKeys.poll()) != null) {
178       Key key = requiredKey.key;
179       if (bindings.containsKey(key)) {
180         continue;
181       }
182       if (!(key.type instanceof Class) || key.annotation != null) {
183         throw new IllegalArgumentException("No binding for " + key);
184       }
185       addJitBinding(key, requiredKey.requiredBy);
186     }
187   }
188 
addJitBinding(Key key, Object requiredBy)189   private void addJitBinding(Key key, Object requiredBy) {
190     Class<?> type = (Class<?>) key.type;
191 
192     /*
193      * Lookup the injectable fields and their corresponding keys.
194      */
195     final List<Field> injectedFields = new ArrayList<>();
196     List<Object> fieldKeysList = new ArrayList<>();
197     for (Class<?> c = type; c != Object.class; c = c.getSuperclass()) {
198       for (Field field : c.getDeclaredFields()) {
199         if (!field.isAnnotationPresent(javax.inject.Inject.class)) {
200           continue;
201         }
202         field.setAccessible(true);
203         injectedFields.add(field);
204         Key fieldKey = key(field, field.getGenericType(), field.getAnnotations());
205         fieldKeysList.add(fieldKey);
206         requireKey(fieldKey, field);
207       }
208     }
209     final Key[] fieldKeys = fieldKeysList.toArray(new Key[fieldKeysList.size()]);
210 
211     /*
212      * Lookup @Inject-annotated constructors. If there's no @Inject-annotated
213      * constructor, use a default constructor if the class has other injections.
214      */
215     Constructor<?> injectedConstructor = null;
216     for (Constructor<?> constructor : type.getDeclaredConstructors()) {
217       if (!constructor.isAnnotationPresent(javax.inject.Inject.class)) {
218         continue;
219       }
220       if (injectedConstructor != null) {
221         throw new IllegalArgumentException("Too many injectable constructors on " + type);
222       }
223       constructor.setAccessible(true);
224       injectedConstructor = constructor;
225     }
226     if (injectedConstructor == null) {
227       if (fieldKeys.length == 0) {
228         throw new IllegalArgumentException(
229             "No injectable constructor on " + type + " required by " + requiredBy);
230       }
231       try {
232         injectedConstructor = type.getConstructor();
233       } catch (NoSuchMethodException e) {
234         throw new IllegalArgumentException(
235             "No injectable constructor on " + type + " required by " + requiredBy);
236       }
237     }
238 
239     /*
240      * Create a provider that invokes the constructor and sets its fields.
241      */
242     final Constructor<?> constructor = injectedConstructor;
243     final Key[] parameterKeys =
244         parametersToKeys(
245             constructor,
246             constructor.getGenericParameterTypes(),
247             constructor.getParameterAnnotations());
248     final Provider<Object> unscoped =
249         new Provider<Object>() {
250           @Override
251           public Object get() {
252             Object[] constructorParameters = keysToValues(parameterKeys);
253             try {
254               Object result = constructor.newInstance(constructorParameters);
255               Object[] fieldValues = keysToValues(fieldKeys);
256               for (int i = 0; i < fieldValues.length; i++) {
257                 injectedFields.get(i).set(result, fieldValues[i]);
258               }
259               return result;
260             } catch (IllegalAccessException e) {
261               throw new RuntimeException(e.getCause());
262             } catch (InvocationTargetException e) {
263               throw new RuntimeException(e.getCause());
264             } catch (InstantiationException e) {
265               throw new RuntimeException(e);
266             }
267           }
268         };
269 
270     boolean singleton = type.isAnnotationPresent(javax.inject.Singleton.class);
271     putBinding(new Key(type, null), unscoped, singleton);
272   }
273 
putBinding(Key key, Provider<Object> provider, boolean singleton)274   private void putBinding(Key key, Provider<Object> provider, boolean singleton) {
275     if (singleton) {
276       singletons.add(key);
277       final Provider<Object> unscoped = provider;
278       provider =
279           new Provider<Object>() {
280             private Object onlyInstance = UNINITIALIZED;
281 
282             @Override
283             public Object get() {
284               if (onlyInstance == UNINITIALIZED) {
285                 onlyInstance = unscoped.get();
286               }
287               return onlyInstance;
288             }
289           };
290     }
291 
292     if (bindings.put(key, provider) != null) {
293       throw new IllegalArgumentException("Duplicate binding " + key);
294     }
295   }
296 
keysToValues(Key[] parameterKeys)297   private Object[] keysToValues(Key[] parameterKeys) {
298     Object[] parameters = new Object[parameterKeys.length];
299     for (int i = 0; i < parameterKeys.length; i++) {
300       parameters[i] = bindings.get(parameterKeys[i]).get();
301     }
302     return parameters;
303   }
304 
parametersToKeys(Member member, Type[] types, Annotation[][] annotations)305   private Key[] parametersToKeys(Member member, Type[] types, Annotation[][] annotations) {
306     final Key[] parameterKeys = new Key[types.length];
307     for (int i = 0; i < parameterKeys.length; i++) {
308       String name = member + " parameter " + i;
309       parameterKeys[i] = key(name, types[i], annotations[i]);
310       requireKey(parameterKeys[i], name);
311     }
312     return parameterKeys;
313   }
314 
key(Object subject, Type type, Annotation[] annotations)315   public Key key(Object subject, Type type, Annotation[] annotations) {
316     Annotation bindingAnnotation = null;
317     for (Annotation a : annotations) {
318       if (!a.annotationType().isAnnotationPresent(javax.inject.Qualifier.class)) {
319         continue;
320       }
321       if (bindingAnnotation != null) {
322         throw new IllegalArgumentException("Too many binding annotations on " + subject);
323       }
324       bindingAnnotation = a;
325     }
326     return new Key(type, bindingAnnotation);
327   }
328 
equal(Object a, Object b)329   private static boolean equal(Object a, Object b) {
330     return a == null ? b == null : a.equals(b);
331   }
332 
333   private static final class Key {
334     final Type type;
335     final Annotation annotation;
336 
Key(Type type, Annotation annotation)337     Key(Type type, Annotation annotation) {
338       this.type = type;
339       this.annotation = annotation;
340     }
341 
342     @Override
equals(Object o)343     public boolean equals(Object o) {
344       return o instanceof Key
345           && ((Key) o).type.equals(type)
346           && equal(annotation, ((Key) o).annotation);
347     }
348 
349     @Override
hashCode()350     public int hashCode() {
351       int result = type.hashCode();
352       if (annotation != null) {
353         result += (37 * annotation.hashCode());
354       }
355       return result;
356     }
357 
358     @Override
toString()359     public String toString() {
360       return "key[type=" + type + ",annotation=" + annotation + "]";
361     }
362   }
363 
364   private static class RequiredKey {
365     private final Key key;
366     private final Object requiredBy;
367 
RequiredKey(Key key, Object requiredBy)368     private RequiredKey(Key key, Object requiredBy) {
369       this.key = key;
370       this.requiredBy = requiredBy;
371     }
372   }
373 
374   private static final class ProviderType implements ParameterizedType {
375     private final Class<?> rawType;
376     private final Type typeArgument;
377 
ProviderType(Class<?> rawType, Type typeArgument)378     public ProviderType(Class<?> rawType, Type typeArgument) {
379       this.rawType = rawType;
380       this.typeArgument = typeArgument;
381     }
382 
383     @Override
getRawType()384     public Type getRawType() {
385       return rawType;
386     }
387 
388     @Override
getActualTypeArguments()389     public Type[] getActualTypeArguments() {
390       return new Type[] {typeArgument};
391     }
392 
393     @Override
getOwnerType()394     public Type getOwnerType() {
395       return null;
396     }
397 
398     @Override
equals(Object o)399     public boolean equals(Object o) {
400       if (o instanceof ParameterizedType) {
401         ParameterizedType that = (ParameterizedType) o;
402         return Arrays.equals(getActualTypeArguments(), that.getActualTypeArguments())
403             && that.getRawType() == rawType;
404       }
405       return false;
406     }
407 
408     @Override
hashCode()409     public int hashCode() {
410       return Arrays.hashCode(getActualTypeArguments()) ^ rawType.hashCode();
411     }
412   }
413 }
414