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;
+  }
+}