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