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