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 */
16package com.google.inject.mini;
17
18import java.lang.annotation.Annotation;
19import java.lang.reflect.Constructor;
20import java.lang.reflect.Field;
21import java.lang.reflect.InvocationTargetException;
22import java.lang.reflect.Member;
23import java.lang.reflect.Method;
24import java.lang.reflect.ParameterizedType;
25import java.lang.reflect.Type;
26import java.util.ArrayDeque;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.HashMap;
30import java.util.HashSet;
31import java.util.List;
32import java.util.Map;
33import java.util.Queue;
34import java.util.Set;
35
36import 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 */
44public final class MiniGuice {
45  private static final Object UNINITIALIZED = new Object();
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   */
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
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
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
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
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
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
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
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
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
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
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
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
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
322    Key(Type type, Annotation annotation) {
323      this.type = type;
324      this.annotation = annotation;
325    }
326
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
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
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
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
360    public ProviderType(Class<?> rawType, Type typeArgument) {
361      this.rawType = rawType;
362      this.typeArgument = typeArgument;
363    }
364
365    public Type getRawType() {
366      return rawType;
367    }
368
369    public Type[] getActualTypeArguments() {
370      return new Type[] { typeArgument };
371    }
372
373    public Type getOwnerType() {
374      return null;
375    }
376
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
386    @Override public int hashCode() {
387      return Arrays.hashCode(getActualTypeArguments()) ^ rawType.hashCode();
388    }
389  }
390}
391