Revision: 1225
Author: [email protected]
Date: Sat Sep 11 01:46:08 2010
Log: MiniGuice, a proof of concept.
The standard Guice injector hits a diverse set of use cases with a small
core. It has advanced features to make developers productive and it's very
fast. And it scales to extremely large teams very well. In a world of
JavaEE aircraft carriers, Guice is a sports car.
To contrast, MiniGuice (placeholder name) is roller skates. It has barely
any features and can't do anything but the bare essentials: field &
constructor injection, provider methods, provider injections, singletons,
and binding annotations. It's intended for use in tiny dozen-file projects
maintained by a single developer in leisure time. It's only features are
its compact size and implementation simplicity.
http://code.google.com/p/google-guice/source/detail?r=1225
Added:
/trunk/extensions/mini
/trunk/extensions/mini/src
/trunk/extensions/mini/src/com
/trunk/extensions/mini/src/com/google
/trunk/extensions/mini/src/com/google/inject
/trunk/extensions/mini/src/com/google/inject/mini
/trunk/extensions/mini/src/com/google/inject/mini/MiniGuice.java
/trunk/extensions/mini/test
/trunk/extensions/mini/test/com
/trunk/extensions/mini/test/com/google
/trunk/extensions/mini/test/com/google/inject
/trunk/extensions/mini/test/com/google/inject/mini
/trunk/extensions/mini/test/com/google/inject/mini/MiniGuiceTest.java
=======================================
--- /dev/null
+++ /trunk/extensions/mini/src/com/google/inject/mini/MiniGuice.java Sat
Sep 11 01:46:08 2010
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.mini;
+
+import com.google.inject.Provider;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+/**
+ * Proof of concept. A tiny injector suitable for tiny applications.
+ *
+ * @author [email protected] (Jesse Wilson)
+ * @since 3.0
+ */
+public final class MiniGuice {
+ private static final Object UNINITIALIZED = new Object();
+ private MiniGuice() {}
+
+ private final Map<Key, Provider<?>> bindings = new HashMap<Key,
Provider<?>>();
+ private final Queue<Key> requiredKeys = new ArrayDeque<Key>();
+ private final Set<Key> singletons = new HashSet<Key>();
+
+ /**
+ * Creates an injector defined by {...@code modules} and immediately uses
it to
+ * create an instance of {...@code type}. The modules can be of any type,
and
+ * must contain {...@code @Provides} methods.
+ *
+ * <p>The following injection features are supported:
+ * <ul>
+ * <li>Field injection. A class may have any number of field
injections, and
+ * fields may be of any visibility. Static fields will be injected
each
+ * time an instance is injected.
+ * <li>Constructor injection. A class may have a single {...@code
+ * @Inject}-annotated constructor. Classes that have fields
injected
+ * may omit the {...@link @Inject} annotation if they have a public
+ * no-arguments constructor.
+ * <li>Injection of {...@code @Provides} method parameters.
+ * <li>{...@code @Provides} methods annotated {...@code @Singleton}.
+ * <li>Constructor-injected classes annotated {...@code @Singleton}.
+ * <li>Injection of {...@link Provider}s.
+ * <li>Binding annotations on injected parameters and fields.
+ * <li>Guice annotations.
+ * <li>JSR 330 annotations.
+ * <li>Eager loading of singletons.
+ * </ul>
+ *
+ * <p><strong>Note that method injection is not supported.</strong>
+ */
+ public static <T> T inject(Class<T> type, Object... modules) {
+ Key key = new Key(type, null);
+ MiniGuice miniGuice = new MiniGuice();
+ for (Object module : modules) {
+ miniGuice.install(module);
+ }
+ miniGuice.requireKey(key);
+ miniGuice.addJitBindings();
+ miniGuice.addProviderBindings();
+ miniGuice.eagerlyLoadSingletons();
+ Provider<?> provider = miniGuice.bindings.get(key);
+ return type.cast(provider.get());
+ }
+
+ private void addProviderBindings() {
+ Map<Key, Provider<?>> providerBindings = new HashMap<Key,
Provider<?>>();
+ for (final Map.Entry<Key, Provider<?>> binding : bindings.entrySet()) {
+ Key key = binding.getKey();
+ final Provider<?> value = binding.getValue();
+ Provider<Provider<?>> providerProvider = new Provider<Provider<?>>()
{
+ public Provider<?> get() {
+ return value;
+ }
+ };
+ providerBindings.put(new Key(new
ProviderType(com.google.inject.Provider.class, key.type),
+ key.annotation), providerProvider);
+ providerBindings.put(new Key(new
ProviderType(javax.inject.Provider.class, key.type),
+ key.annotation), providerProvider);
+ }
+ bindings.putAll(providerBindings);
+ }
+
+ private void requireKey(Key key) {
+ if (key.type instanceof ParameterizedType
+ && (((ParameterizedType) key.type).getRawType() == Provider.class
+ || ((ParameterizedType) key.type).getRawType() ==
javax.inject.Provider.class)) {
+ Type type = ((ParameterizedType)
key.type).getActualTypeArguments()[0];
+ key = new Key(type, key.annotation);
+ }
+
+ requiredKeys.add(key);
+ }
+
+ private void eagerlyLoadSingletons() {
+ for (Key key : singletons) {
+ Provider<?> provider = bindings.get(key);
+ final Object onlyInstance = provider.get();
+ bindings.put(key, new Provider<Object>() {
+ public Object get() {
+ return onlyInstance;
+ }
+ });
+ }
+ }
+
+ public void install(Object module) {
+ boolean hasProvidesMethods = false;
+ for (Class<?> c = module.getClass(); c != Object.class; c =
c.getSuperclass()) {
+ for (Method method : c.getDeclaredMethods()) {
+ if (method.getAnnotation(com.google.inject.Provides.class) !=
null) {
+ Key key = key(method, method.getGenericReturnType(),
method.getAnnotations());
+ addProviderMethodBinding(key, module, method);
+ hasProvidesMethods = true;
+ }
+ }
+ }
+ if (!hasProvidesMethods) {
+ throw new IllegalArgumentException("No @Provides methods on " +
module);
+ }
+ }
+
+ private void addProviderMethodBinding(Key key, final Object instance,
final Method method) {
+ final Key[] parameterKeys = parametersToKeys(
+ method, method.getGenericParameterTypes(),
method.getParameterAnnotations());
+ final Provider<Object> unscoped = new Provider<Object>() {
+ public Object get() {
+ Object[] parameters = keysToValues(parameterKeys);
+ try {
+ return method.invoke(instance, parameters);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ }
+ }
+ };
+
+ boolean singleton =
method.getAnnotation(com.google.inject.Singleton.class) != null
+ || method.getAnnotation(javax.inject.Singleton.class) != null;
+ putBinding(key, unscoped, singleton);
+ }
+
+ private void addJitBindings() {
+ Key requiredKey;
+ while ((requiredKey = requiredKeys.poll()) != null) {
+ if (bindings.containsKey(requiredKey)) {
+ continue;
+ }
+ if (!(requiredKey.type instanceof Class)) {
+ throw new IllegalArgumentException("No binding for " +
requiredKey);
+ }
+ addJitBinding(requiredKey);
+ }
+ }
+
+ private void addJitBinding(Key key) {
+ Class<?> type = (Class<?>) key.type;
+
+ /*
+ * Lookup the injectable fields and their corresponding keys.
+ */
+ final List<Field> injectedFields = new ArrayList<Field>();
+ List<Object> fieldKeysList = new ArrayList<Object>();
+ for (Class<?> c = type; c != Object.class; c = c.getSuperclass()) {
+ for (Field field : type.getDeclaredFields()) {
+ if (field.getAnnotation(com.google.inject.Inject.class) == null
+ && field.getAnnotation(javax.inject.Inject.class) == null) {
+ continue;
+ }
+ injectedFields.add(field);
+ Key fieldKey = key(field, field.getGenericType(),
field.getAnnotations());
+ fieldKeysList.add(fieldKey);
+ requireKey(fieldKey);
+ }
+ }
+ final Key[] fieldKeys = fieldKeysList.toArray(new
Key[fieldKeysList.size()]);
+
+ /*
+ * Lookup @Inject-annotated constructors. If there's no
@Inject-annotated
+ * constructor, use a default constructor if the class has other
injections.
+ */
+ Constructor<?> injectedConstructor = null;
+ for (Constructor<?> constructor : type.getDeclaredConstructors()) {
+ if (constructor.getAnnotation(com.google.inject.Inject.class) == null
+ && constructor.getAnnotation(javax.inject.Inject.class) == null)
{
+ continue;
+ }
+ if (injectedConstructor != null) {
+ throw new IllegalArgumentException("Too many injectable
constructors on " + type);
+ }
+ injectedConstructor = constructor;
+ }
+ if (injectedConstructor == null) {
+ if (fieldKeys.length == 0) {
+ throw new IllegalArgumentException("No injectable constructor on "
+ type);
+ }
+ try {
+ injectedConstructor = type.getConstructor();
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("No injectable constructor on "
+ type);
+ }
+ }
+
+ /*
+ * Create a provider that invokes the constructor and sets its fields.
+ */
+ final Constructor<?> constructor = injectedConstructor;
+ final Key[] parameterKeys = parametersToKeys(
+ constructor, constructor.getGenericParameterTypes(),
constructor.getParameterAnnotations());
+ final Provider<Object> unscoped = new Provider<Object>() {
+ public Object get() {
+ Object[] constructorParameters = keysToValues(parameterKeys);
+ try {
+ Object result = constructor.newInstance(constructorParameters);
+ Object[] fieldValues = keysToValues(fieldKeys);
+ for (int i = 0; i < fieldValues.length; i++) {
+ injectedFields.get(i).set(result, fieldValues[i]);
+ }
+ return result;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getCause());
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+ boolean singleton =
type.getAnnotation(com.google.inject.Singleton.class) != null
+ || type.getAnnotation(javax.inject.Singleton.class) != null;
+ putBinding(new Key(type, null), unscoped, singleton);
+ }
+
+ private void putBinding(Key key, Provider<Object> provider, boolean
singleton) {
+ if (singleton) {
+ final Provider<Object> unscoped = provider;
+ provider = new Provider<Object>() {
+ private Object onlyInstance = UNINITIALIZED;
+ public Object get() {
+ if (onlyInstance == UNINITIALIZED) {
+ onlyInstance = unscoped.get();
+ }
+ return onlyInstance;
+ }
+ };
+ }
+
+ if (bindings.put(key, provider) != null) {
+ throw new IllegalArgumentException("Duplicate binding " + key);
+ }
+ }
+
+ private Object[] keysToValues(Key[] parameterKeys) {
+ Object[] parameters = new Object[parameterKeys.length];
+ for (int i = 0; i < parameterKeys.length; i++) {
+ parameters[i] = bindings.get(parameterKeys[i]).get();
+ }
+ return parameters;
+ }
+
+ private Key[] parametersToKeys(Member member, Type[] types,
Annotation[][] annotations) {
+ final Key[] parameterKeys = new Key[types.length];
+ for (int i = 0; i < parameterKeys.length; i++) {
+ parameterKeys[i] = key(member + " parameter " + i, types[i],
annotations[i]);
+ requireKey(parameterKeys[i]);
+ }
+ return parameterKeys;
+ }
+
+ public Key key(Object subject, Type type, Annotation[] annotations) {
+ Annotation bindingAnnotation = null;
+ for (Annotation a : annotations) {
+ if (a.annotationType().getAnnotation(javax.inject.Qualifier.class)
== null
+ &&
a.annotationType().getAnnotation(com.google.inject.BindingAnnotation.class)
== null) {
+ continue;
+ }
+ if (bindingAnnotation != null) {
+ throw new IllegalArgumentException("Too many binding annotations
on " + subject);
+ }
+ bindingAnnotation = a;
+ }
+ return new Key(type, bindingAnnotation);
+ }
+
+ private static boolean equal(Object a, Object b) {
+ return a == null ? b == null : a.equals(b);
+ }
+
+ private static final class Key {
+ final Type type;
+ final Annotation annotation;
+
+ Key(Type type, Annotation annotation) {
+ this.type = type;
+ this.annotation = annotation;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Key
+ && ((Key) o).type.equals(type)
+ && equal(annotation, ((Key) o).annotation);
+ }
+
+ @Override public int hashCode() {
+ int result = type.hashCode();
+ if (annotation != null) {
+ result += (37 * annotation.hashCode());
+ }
+ return result;
+ }
+
+ @Override public String toString() {
+ return "key[type=" + type + ",annotation=" + annotation + "]";
+ }
+ }
+
+ private static final class ProviderType implements ParameterizedType {
+ private final Class<?> rawType;
+ private final Type typeArgument;
+
+ public ProviderType(Class<?> rawType, Type typeArgument) {
+ this.rawType = rawType;
+ this.typeArgument = typeArgument;
+ }
+
+ public Type getRawType() {
+ return rawType;
+ }
+
+ public Type[] getActualTypeArguments() {
+ return new Type[] { typeArgument };
+ }
+
+ public Type getOwnerType() {
+ return null;
+ }
+
+ @Override public boolean equals(Object o) {
+ if (o instanceof ParameterizedType) {
+ ParameterizedType that = (ParameterizedType) o;
+ return Arrays.equals(getActualTypeArguments(),
that.getActualTypeArguments())
+ && that.getRawType() == rawType;
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ return Arrays.hashCode(getActualTypeArguments()) ^
rawType.hashCode();
+ }
+ }
+}
=======================================
--- /dev/null
+++ /trunk/extensions/mini/test/com/google/inject/mini/MiniGuiceTest.java
Sat Sep 11 01:46:08 2010
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.mini;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.inject.Named;
+import junit.framework.TestCase;
+
+public final class MiniGuiceTest extends TestCase {
+
+ public void testBasicInjection() {
+ G g = MiniGuice.inject(G.class, new Object() {
+ @Provides E provideE(F f) {
+ return new E(f);
+ }
+ @Provides F provideF() {
+ return new F();
+ }
+ });
+
+ assertNotNull(g.a);
+ assertNotNull(g.b);
+ assertNotNull(g.c);
+ assertNotNull(g.d);
+ assertNotNull(g.e);
+ assertNotNull(g.e.f);
+ }
+
+ static class A {
+ @Inject A() {}
+ }
+
+ static class B {
+ @Inject B() {}
+ }
+
+ @Singleton
+ static class C {
+ @Inject C() {}
+ }
+
+ @Singleton
+ static class D {
+ @Inject D() {}
+ }
+
+ static class E {
+ F f;
+ E(F f) {
+ this.f = f;
+ }
+ }
+
+ static class F {}
+
+ static class G {
+ @Inject A a;
+ @Inject B b;
+ C c;
+ D d;
+ @Inject E e;
+ @Inject G(C c, D d) {
+ this.c = c;
+ this.d = d;
+ }
+ }
+
+ public void testProviderInjection() {
+ H h = MiniGuice.inject(H.class);
+ assertNotNull(h.aProvider.get());
+ assertNotNull(h.aProvider.get());
+ assertNotSame(h.aProvider.get(), h.aProvider.get());
+ }
+
+ static class H {
+ @Inject Provider<A> aProvider;
+ @Inject H() {}
+ }
+
+ public void testSingletons() {
+ J j = MiniGuice.inject(J.class, new Object() {
+ @Provides @Singleton F provideK() {
+ return new F();
+ }
+ });
+ assertSame(j.fProvider.get(), j.fProvider.get());
+ assertSame(j.iProvider.get(), j.iProvider.get());
+ }
+
+ @Singleton
+ static class I {
+ @Inject I() {}
+ }
+
+ static class J {
+ @Inject Provider<F> fProvider;
+ @Inject Provider<I> iProvider;
+ @Inject J() {}
+ }
+
+ public void testBindingAnnotations() {
+ final A one = new A();
+ final A two = new A();
+
+ K k = MiniGuice.inject(K.class, new Object() {
+ @Provides @Named("one") A getOne() {
+ return one;
+ }
+ @Provides @Named("two") A getTwo() {
+ return two;
+ }
+ });
+
+ assertNotNull(k.a);
+ assertSame(one, k.aOne);
+ assertSame(two, k.aTwo);
+ }
+
+ public static class K {
+ @Inject A a;
+ @Inject @Named("one") A aOne;
+ @Inject @Named("two") A aTwo;
+ }
+
+ public void testSingletonBindingAnnotationAndProvider() {
+ final AtomicReference<A> a1 = new AtomicReference<A>();
+ final AtomicReference<A> a2 = new AtomicReference<A>();
+
+ L l = MiniGuice.inject(L.class, new Object() {
+ @Provides @Singleton @Named("one") F provideF(Provider<A> aProvider)
{
+ a1.set(aProvider.get());
+ a2.set(aProvider.get());
+ return new F();
+ }
+ });
+
+ assertNotNull(a1.get());
+ assertNotNull(a2.get());
+ assertNotSame(a1.get(), a2.get());
+ assertSame(l, l.lProvider.get());
+ }
+
+ @Singleton
+ public static class L {
+ @Inject @Named("one") F f;
+ @Inject Provider<L> lProvider;
+ }
+
+ public void testSingletonInGraph() {
+ M m = MiniGuice.inject(M.class, new Object() {
+ @Provides @Singleton F provideF() {
+ return new F();
+ }
+ });
+
+ assertSame(m.f1, m.f2);
+ assertSame(m.f1, m.n1.f1);
+ assertSame(m.f1, m.n1.f2);
+ assertSame(m.f1, m.n2.f1);
+ assertSame(m.f1, m.n2.f2);
+ assertSame(m.f1, m.n1.fProvider.get());
+ assertSame(m.f1, m.n2.fProvider.get());
+ }
+
+ public static class M {
+ @Inject N n1;
+ @Inject N n2;
+ @Inject F f1;
+ @Inject F f2;
+ }
+
+ public static class N {
+ @Inject F f1;
+ @Inject F f2;
+ @Inject Provider<F> fProvider;
+ }
+}
--
You received this message because you are subscribed to the Google Groups
"google-guice-dev" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-guice-dev?hl=en.