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