Guice now reifies types! If you inject a TypeLiteral<T>, Guice will deduce what T is and inject the proper TypeLiteral for you.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@668 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index a827c46..8a785ce 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -19,6 +19,7 @@
import com.google.common.base.Nullable;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
@@ -30,6 +31,7 @@
import com.google.inject.internal.FailableCache;
import com.google.inject.internal.MatcherAndConverter;
import com.google.inject.internal.MoreTypes;
+import com.google.inject.internal.SourceProvider;
import com.google.inject.internal.ToStringBuilder;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
@@ -39,6 +41,7 @@
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
@@ -351,6 +354,14 @@
throw errors.missingImplementation(key).toException();
}
+ // Handle TypeLiteral<T> by binding the inner type
+ if (rawType == TypeLiteral.class) {
+ @SuppressWarnings("unchecked") // we have to fudge the inner type as Object
+ BindingImpl<T> binding = (BindingImpl<T>) createTypeLiteralBinding(
+ (Key<TypeLiteral<Object>>) key, errors);
+ return binding;
+ }
+
// Handle @ImplementedBy
ImplementedBy implementedBy = rawType.getAnnotation(ImplementedBy.class);
if (implementedBy != null) {
@@ -395,6 +406,36 @@
this, key, source, scopedFactory, scope, lateBoundConstructor, loadStrategy);
}
+ /**
+ * Converts a binding for a {@code Key<TypeLiteral<T>>} to the value {@code TypeLiteral<T>}. It's
+ * a bit awkward because we have to pull out the inner type in the type literal.
+ */
+ private <T> BindingImpl<TypeLiteral<T>> createTypeLiteralBinding(
+ Key<TypeLiteral<T>> key, Errors errors) throws ErrorsException {
+ Type typeLiteralType = key.getTypeLiteral().getType();
+ if (!(typeLiteralType instanceof ParameterizedType)) {
+ throw errors.cannotInjectRawTypeLiteral().toException();
+ }
+
+ ParameterizedType parameterizedType = (ParameterizedType) typeLiteralType;
+ Type innerType = parameterizedType.getActualTypeArguments()[0];
+
+ // this is unforunate. We don't support building TypeLiterals for type variable like 'T'. If
+ // this proves problematic, we can probably fix TypeLiteral to support type variables
+ if (!(innerType instanceof Class)
+ && !(innerType instanceof GenericArrayType)
+ && !(innerType instanceof ParameterizedType)) {
+ throw errors.cannotInjectTypeLiteralOf(innerType).toException();
+ }
+
+ @SuppressWarnings("unchecked") // by definition, innerType == T, so this is safe
+ TypeLiteral<T> value = (TypeLiteral<T>) TypeLiteral.get(innerType);
+ InternalFactory<TypeLiteral<T>> factory = new ConstantFactory<TypeLiteral<T>>(
+ Initializables.of(value));
+ return new InstanceBindingImpl<TypeLiteral<T>>(this, key, SourceProvider.UNKNOWN_SOURCE,
+ factory, ImmutableSet.<InjectionPoint>of(), value);
+ }
+
static class LateBoundConstructor<T> implements InternalFactory<T> {
ConstructorInjector<T> constructorInjector;
diff --git a/src/com/google/inject/internal/Errors.java b/src/com/google/inject/internal/Errors.java
index 35c8bb3..1c6861d 100644
--- a/src/com/google/inject/internal/Errors.java
+++ b/src/com/google/inject/internal/Errors.java
@@ -36,6 +36,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
+import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -287,6 +288,14 @@
return addMessage("Cannot inject a Provider that has no type parameter");
}
+ public Errors cannotInjectTypeLiteralOf(Type unsupportedType) {
+ return addMessage("Cannot inject a TypeLiteral of %s", unsupportedType);
+ }
+
+ public Errors cannotInjectRawTypeLiteral() {
+ return addMessage("Cannot inject a TypeLiteral that has no type parameter");
+ }
+
public Errors cannotSatisfyCircularDependency(Class<?> expectedType) {
return addMessage(
"Tried proxying %s to support a circular dependency, but it is not an interface.",
diff --git a/test/com/google/inject/TypeLiteralInjectionTest.java b/test/com/google/inject/TypeLiteralInjectionTest.java
new file mode 100644
index 0000000..b0fe98f
--- /dev/null
+++ b/test/com/google/inject/TypeLiteralInjectionTest.java
@@ -0,0 +1,106 @@
+/**
+ * Copyright (C) 2008 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;
+
+import static com.google.inject.Asserts.assertContains;
+import com.google.inject.util.Types;
+import static com.google.inject.util.Types.listOf;
+import java.lang.reflect.Type;
+import java.util.List;
+import junit.framework.TestCase;
+
+/**
+ * Demonstrates type reification.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class TypeLiteralInjectionTest extends TestCase {
+
+ public void testBindingToRawTypeLiteralIsNotAllowed() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(TypeLiteral.class).toInstance(TypeLiteral.get(String.class));
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertContains(expected.getMessage(),
+ "Binding to core guice framework type is not allowed: TypeLiteral");
+ }
+ }
+
+ public void testBindingToParameterizedTypeLiteralIsNotAllowed() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(new TypeLiteral<TypeLiteral<String>>() {})
+ .toInstance(TypeLiteral.get(String.class));
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertContains(expected.getMessage(),
+ "Binding to core guice framework type is not allowed: TypeLiteral");
+ }
+ }
+
+ public void testInjectTypeLiteralWithRawTypes() {
+ Type t = A.class.getTypeParameters()[0];
+
+ A a = Guice.createInjector().getInstance(A.class);
+ assertEquals(TypeLiteral.get(String.class), a.string);
+ assertEquals(TypeLiteral.get(listOf(t)), a.listOfT);
+ assertEquals(TypeLiteral.get(listOf(Types.subtypeOf(t))), a.listOfWildcardT);
+
+ try {
+ Guice.createInjector().getInstance(B.class);
+ fail();
+ } catch (ConfigurationException expected) {
+ assertContains(expected.getMessage(), "Cannot inject a TypeLiteral of T",
+ "while locating com.google.inject.TypeLiteral<T>");
+ }
+ }
+
+ public void testInjectTypeLiteralWithClassTypes() {
+ B<Integer> b = Guice.createInjector().getInstance(new Key<B<Integer>>() {});
+ assertEquals(TypeLiteral.get(String.class), b.string);
+ assertEquals(TypeLiteral.get(Integer.class), b.t);
+ assertEquals(TypeLiteral.get(listOf(Integer.class)), b.listOfT);
+ assertEquals(TypeLiteral.get(listOf(Types.subtypeOf(Integer.class))), b.listOfWildcardT);
+ }
+
+ public void testInjectRawTypeLiteral() {
+ try {
+ Guice.createInjector().getInstance(TypeLiteral.class);
+ fail();
+ } catch (ConfigurationException expected) {
+ assertContains(expected.getMessage(),
+ "Cannot inject a TypeLiteral that has no type parameter");
+ }
+ }
+
+ static class A<T> {
+ @Inject TypeLiteral<String> string;
+ @Inject TypeLiteral<List<T>> listOfT;
+ @Inject TypeLiteral<List<? extends T>> listOfWildcardT;
+ }
+
+ static class B<T> extends A<T> {
+ @Inject TypeLiteral<T> t;
+ }
+}