Add Binder.requireAtInjectOnConstructors, to force Guice to require @Inject annotations on constructors.

Revision created by MOE tool push_codebase.
MOE_MIGRATION=4906
diff --git a/core/src/com/google/inject/Binder.java b/core/src/com/google/inject/Binder.java
index 8bf9a38..441b6ad 100644
--- a/core/src/com/google/inject/Binder.java
+++ b/core/src/com/google/inject/Binder.java
@@ -470,4 +470,17 @@
    * @since 3.0
    */
   void disableCircularProxies();
+  
+  /**
+   * Requires that a {@literal @}{@link Inject} annotation exists on a constructor in order for
+   * Guice to consider it an eligible injectable class. By default, Guice will inject classes that
+   * have a no-args constructor if no {@literal @}{@link Inject} annotation exists on any
+   * constructor.
+   * <p>
+   * If the class is bound using {@link LinkedBindingBuilder#toConstructor}, Guice will still inject
+   * that constructor regardless of annotations.
+   *
+   * @since 4.0
+   */
+  void requireAtInjectOnConstructors();
 }
diff --git a/core/src/com/google/inject/internal/BindingProcessor.java b/core/src/com/google/inject/internal/BindingProcessor.java
index 713f98d..a87e326 100644
--- a/core/src/com/google/inject/internal/BindingProcessor.java
+++ b/core/src/com/google/inject/internal/BindingProcessor.java
@@ -72,7 +72,7 @@
         prepareBinding();
         try {
           ConstructorBindingImpl<T> onInjector = ConstructorBindingImpl.create(injector, key, 
-              binding.getConstructor(), source, scoping, errors, false);
+              binding.getConstructor(), source, scoping, errors, false, false);
           scheduleInitialization(onInjector);
           putBinding(onInjector);
         } catch (ErrorsException e) {
diff --git a/core/src/com/google/inject/internal/ConstructorBindingImpl.java b/core/src/com/google/inject/internal/ConstructorBindingImpl.java
index 0bbce3f..f8fbfd2 100644
--- a/core/src/com/google/inject/internal/ConstructorBindingImpl.java
+++ b/core/src/com/google/inject/internal/ConstructorBindingImpl.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.inject.Binder;
 import com.google.inject.ConfigurationException;
+import com.google.inject.Inject;
 import com.google.inject.Key;
 import com.google.inject.TypeLiteral;
 import com.google.inject.internal.util.Classes;
@@ -71,7 +72,7 @@
    */
   static <T> ConstructorBindingImpl<T> create(InjectorImpl injector, Key<T> key, 
       InjectionPoint constructorInjector, Object source, Scoping scoping, Errors errors,
-      boolean failIfNotLinked)
+      boolean failIfNotLinked, boolean failIfNotExplicit)
       throws ErrorsException {
     int numErrors = errors.size();
 
@@ -96,6 +97,9 @@
     if (constructorInjector == null) {
       try {
         constructorInjector = InjectionPoint.forConstructorOf(key.getTypeLiteral());
+        if (failIfNotExplicit && !hasAtInject((Constructor) constructorInjector.getMember())) {
+          errors.atInjectRequired(rawType);
+        }
       } catch (ConfigurationException e) {
         throw errors.merge(e.getErrorMessages()).toException();
       }
@@ -120,6 +124,12 @@
     return new ConstructorBindingImpl<T>(
         injector, key, source, scopedFactory, scoping, factoryFactory, constructorInjector);
   }
+  
+  /** Returns true if the inject annotation is on the constructor. */
+  private static boolean hasAtInject(Constructor cxtor) {
+    return cxtor.isAnnotationPresent(Inject.class)
+        || cxtor.isAnnotationPresent(javax.inject.Inject.class);
+  }
 
   @SuppressWarnings("unchecked") // the result type always agrees with the ConstructorInjector type
   public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
diff --git a/core/src/com/google/inject/internal/Errors.java b/core/src/com/google/inject/internal/Errors.java
index 4eddfe8..13c5cee 100644
--- a/core/src/com/google/inject/internal/Errors.java
+++ b/core/src/com/google/inject/internal/Errors.java
@@ -139,6 +139,13 @@
     return addMessage("Explicit bindings are required and %s is not explicitly bound.", key);
   }
 
+  public Errors atInjectRequired(Class clazz) {
+    return addMessage(
+        "Explicit @Inject annotations are required on constructors,"
+        + " but %s has no constructors annotated with @Inject.",
+        clazz);
+  }
+
   public Errors converterReturnedNull(String stringValue, Object source,
       TypeLiteral<?> type, TypeConverterBinding typeConverterBinding) {
     return addMessage("Received null converting '%s' (bound at %s) to %s%n"
@@ -647,17 +654,17 @@
 
   private static final Collection<Converter<?>> converters = ImmutableList.of(
       new Converter<Class>(Class.class) {
-        public String toString(Class c) {
+        @Override public String toString(Class c) {
           return c.getName();
         }
       },
       new Converter<Member>(Member.class) {
-        public String toString(Member member) {
+        @Override public String toString(Member member) {
           return Classes.toString(member);
         }
       },
       new Converter<Key>(Key.class) {
-        public String toString(Key key) {
+        @Override public String toString(Key key) {
           if (key.getAnnotationType() != null) {
             return key.getTypeLiteral() + " annotated with "
                 + (key.getAnnotation() != null ? key.getAnnotation() : key.getAnnotationType());
diff --git a/core/src/com/google/inject/internal/InjectorImpl.java b/core/src/com/google/inject/internal/InjectorImpl.java
index 49fd265..ee5e2d6 100644
--- a/core/src/com/google/inject/internal/InjectorImpl.java
+++ b/core/src/com/google/inject/internal/InjectorImpl.java
@@ -71,11 +71,14 @@
     final Stage stage;
     final boolean jitDisabled;
     final boolean disableCircularProxies;
+    final boolean atInjectRequired;
     
-    InjectorOptions(Stage stage, boolean jitDisabled, boolean disableCircularProxies) {
+    InjectorOptions(Stage stage, boolean jitDisabled, boolean disableCircularProxies,
+        boolean atInjectRequired) {
       this.stage = stage;
       this.jitDisabled = jitDisabled;
       this.disableCircularProxies = disableCircularProxies;
+      this.atInjectRequired = atInjectRequired;
     }
     
     @Override
@@ -84,6 +87,7 @@
           .add("stage", stage)
           .add("jitDisabled", jitDisabled)
           .add("disableCircularProxies", disableCircularProxies)
+          .add("atInjectRequired", atInjectRequired)
           .toString();
     }
   }
@@ -179,7 +183,7 @@
     if(isProvider(key)) {
       try {
         // This is safe because isProvider above ensures that T is a Provider<?>
-        @SuppressWarnings("unchecked")
+        @SuppressWarnings({"unchecked", "cast"})
         Key<?> providedKey = (Key<?>)getProvidedKey((Key)key, new Errors());
         if(getExistingBinding(providedKey) != null) {
           return getBinding(key);
@@ -651,7 +655,14 @@
     }
 
     
-    return ConstructorBindingImpl.create(this, key, null, source, scoping, errors, jitBinding && options.jitDisabled);
+    return ConstructorBindingImpl.create(this,
+        key,
+        null, /* use default constructor */
+        source,
+        scoping,
+        errors,
+        jitBinding && options.jitDisabled,
+        options.atInjectRequired);
   }
 
   /**
@@ -813,7 +824,7 @@
     if (isProvider(key)) {
       // These casts are safe. We know T extends Provider<X> and that given Key<Provider<X>>,
       // createProviderBinding() will return BindingImpl<Provider<X>>.
-      @SuppressWarnings("unchecked")
+      @SuppressWarnings({"unchecked", "cast"})
       BindingImpl<T> binding = (BindingImpl<T>) createProviderBinding((Key) key, errors);
       return binding;
     }
@@ -822,7 +833,7 @@
     if (isMembersInjector(key)) {
       // These casts are safe. T extends MembersInjector<X> and that given Key<MembersInjector<X>>,
       // createMembersInjectorBinding() will return BindingImpl<MembersInjector<X>>.
-      @SuppressWarnings("unchecked")
+      @SuppressWarnings({"unchecked", "cast"})
       BindingImpl<T> binding = (BindingImpl<T>) createMembersInjectorBinding((Key) key, errors);
       return binding;
     }
diff --git a/core/src/com/google/inject/internal/InjectorOptionsProcessor.java b/core/src/com/google/inject/internal/InjectorOptionsProcessor.java
index 4434dce..b21b696 100644
--- a/core/src/com/google/inject/internal/InjectorOptionsProcessor.java
+++ b/core/src/com/google/inject/internal/InjectorOptionsProcessor.java
@@ -22,6 +22,7 @@
 import com.google.inject.Stage;
 import com.google.inject.internal.InjectorImpl.InjectorOptions;
 import com.google.inject.spi.DisableCircularProxiesOption;
+import com.google.inject.spi.RequireAtInjectOnConstructorsOption;
 import com.google.inject.spi.RequireExplicitBindingsOption;
 
 /**
@@ -33,6 +34,7 @@
 
   private boolean disableCircularProxies = false;
   private boolean jitDisabled = false;
+  private boolean atInjectRequired = false;
 
   InjectorOptionsProcessor(Errors errors) {
     super(errors);
@@ -49,6 +51,12 @@
     jitDisabled = true;
     return true;
   }
+  
+  @Override
+  public Boolean visit(RequireAtInjectOnConstructorsOption option) {
+    atInjectRequired = true;
+    return true;    
+  }  
 
   InjectorOptions getOptions(Stage stage, InjectorOptions parentOptions) {
     checkNotNull(stage, "stage must be set");
@@ -56,13 +64,15 @@
       return new InjectorOptions(
           stage,
           jitDisabled,
-          disableCircularProxies);
+          disableCircularProxies,
+          atInjectRequired);
     } else {
       checkState(stage == parentOptions.stage, "child & parent stage don't match");
       return new InjectorOptions(
           stage,
           jitDisabled || parentOptions.jitDisabled,
-          disableCircularProxies || parentOptions.disableCircularProxies); 
+          disableCircularProxies || parentOptions.disableCircularProxies,
+          atInjectRequired || parentOptions.atInjectRequired); 
     }
   }
 
diff --git a/core/src/com/google/inject/spi/DefaultElementVisitor.java b/core/src/com/google/inject/spi/DefaultElementVisitor.java
index cfceea7..573df35 100644
--- a/core/src/com/google/inject/spi/DefaultElementVisitor.java
+++ b/core/src/com/google/inject/spi/DefaultElementVisitor.java
@@ -94,4 +94,8 @@
   public V visit(RequireExplicitBindingsOption option) {
     return visitOther(option);
   }
+  
+  public V visit(RequireAtInjectOnConstructorsOption option) {
+    return visitOther(option);
+  }
 }
diff --git a/core/src/com/google/inject/spi/ElementVisitor.java b/core/src/com/google/inject/spi/ElementVisitor.java
index 6ebc8cc..4dc65d7 100644
--- a/core/src/com/google/inject/spi/ElementVisitor.java
+++ b/core/src/com/google/inject/spi/ElementVisitor.java
@@ -17,6 +17,7 @@
 package com.google.inject.spi;
 
 import com.google.inject.Binding;
+import com.google.inject.Inject;
 
 /**
  * Visit elements.
@@ -105,4 +106,11 @@
    * @since 3.0
    */
   V visit(DisableCircularProxiesOption option);
+  
+  /**
+   * Visit a require explicit {@literal @}{@link Inject} command.
+   * 
+   * @since 4.0
+   */
+  V visit(RequireAtInjectOnConstructorsOption option);
 }
diff --git a/core/src/com/google/inject/spi/Elements.java b/core/src/com/google/inject/spi/Elements.java
index e2737a1..553d65c 100644
--- a/core/src/com/google/inject/spi/Elements.java
+++ b/core/src/com/google/inject/spi/Elements.java
@@ -315,6 +315,10 @@
     public void requireExplicitBindings() {
       elements.add(new RequireExplicitBindingsOption(getSource()));     
     }
+    
+    public void requireAtInjectOnConstructors() {
+      elements.add(new RequireAtInjectOnConstructorsOption(getSource()));
+    }
 
     public void expose(Key<?> key) {
       exposeInternal(key);
diff --git a/core/src/com/google/inject/spi/InjectionPoint.java b/core/src/com/google/inject/spi/InjectionPoint.java
index 891c42d..76197e3 100644
--- a/core/src/com/google/inject/spi/InjectionPoint.java
+++ b/core/src/com/google/inject/spi/InjectionPoint.java
@@ -444,6 +444,7 @@
       this.field = field;
     }
 
+    @Override
     InjectionPoint toInjectionPoint() {
       return new InjectionPoint(declaringType, field, optional);
     }
@@ -463,6 +464,7 @@
       this.method = method;
     }
 
+    @Override
     InjectionPoint toInjectionPoint() {
       return new InjectionPoint(declaringType, method, optional);
     }
diff --git a/core/src/com/google/inject/spi/RequireAtInjectOnConstructorsOption.java b/core/src/com/google/inject/spi/RequireAtInjectOnConstructorsOption.java
new file mode 100644
index 0000000..03d8c34
--- /dev/null
+++ b/core/src/com/google/inject/spi/RequireAtInjectOnConstructorsOption.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2012 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.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.inject.Binder;
+import com.google.inject.Inject;
+
+/**
+ * A request to require explicit {@literal @}{@link Inject} annotations on constructors.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ * @since 4.0
+ */
+public final class RequireAtInjectOnConstructorsOption implements Element {
+  private final Object source;
+
+  RequireAtInjectOnConstructorsOption(Object source) {
+    this.source = checkNotNull(source, "source");
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public void applyTo(Binder binder) {
+    binder.withSource(getSource()).requireAtInjectOnConstructors();
+  }
+
+  public <T> T acceptVisitor(ElementVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+}
diff --git a/core/test/com/google/inject/AllTests.java b/core/test/com/google/inject/AllTests.java
index be5b875..d6b59f3 100644
--- a/core/test/com/google/inject/AllTests.java
+++ b/core/test/com/google/inject/AllTests.java
@@ -36,6 +36,7 @@
 import com.google.inject.util.NoopOverrideTest;
 import com.google.inject.util.ProvidersTest;
 import com.google.inject.util.TypesTest;
+
 import com.googlecode.guice.GuiceTck;
 import com.googlecode.guice.Jsr330Test;
 
@@ -92,6 +93,7 @@
     // ProxyFactoryTest is AOP-only
     suite.addTestSuite(ReflectionTest.class);
     suite.addTestSuite(RequestInjectionTest.class);
+    suite.addTestSuite(RequireAtInjectOnConstructorsTest.class);
     suite.addTestSuite(ScopesTest.class);
     suite.addTestSuite(SerializationTest.class);
     suite.addTestSuite(SuperclassTest.class);
diff --git a/core/test/com/google/inject/RequireAtInjectOnConstructorsTest.java b/core/test/com/google/inject/RequireAtInjectOnConstructorsTest.java
new file mode 100644
index 0000000..e5f90c2
--- /dev/null
+++ b/core/test/com/google/inject/RequireAtInjectOnConstructorsTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2012 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 junit.framework.TestCase;
+
+/**
+ * Tests for {@link Binder#requireAtInjectOnConstructors()}
+ * 
+ * @author sameb@google.com (Sam Berlin)
+ */
+public class RequireAtInjectOnConstructorsTest extends TestCase {
+  
+  public void testNoCxtors_explicitBinding() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          bind(NoCxtors.class);
+          binder().requireAtInjectOnConstructors();
+        }
+      });
+      fail();
+    } catch (CreationException ce) {
+      assertEquals(1, ce.getErrorMessages().size());
+      Asserts.assertContains(ce.getMessage(),
+          "1) Explicit @Inject annotations are required on constructors, but "
+          + NoCxtors.class.getName() + " has no constructors annotated with @Inject",
+          "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure");
+    }
+  }
+  
+  public void testNoCxtors_jitBinding() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      @Override
+      protected void configure() {
+        binder().requireAtInjectOnConstructors();
+      }
+    });
+    try {
+      injector.getInstance(NoCxtors.class);
+      fail();
+    } catch (ConfigurationException ce) {
+      Asserts.assertContains(ce.getMessage(),
+          "1) Explicit @Inject annotations are required on constructors, but "
+          + NoCxtors.class.getName() + " has no constructors annotated with @Inject",
+          "while locating " + NoCxtors.class.getName());
+    }
+  }
+  
+  public void testNoCxtors_implicitBinding() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          bind(Interface.class).to(NoCxtors.class);
+          binder().requireAtInjectOnConstructors();
+        }
+      });
+      fail();
+    } catch (CreationException ce) {
+      assertEquals(1, ce.getErrorMessages().size());
+      Asserts.assertContains(ce.getMessage(),
+          "1) Explicit @Inject annotations are required on constructors, but "
+          + NoCxtors.class.getName() + " has no constructors annotated with @Inject",
+          "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure");
+    }
+  }
+  
+  public void testNoCxtors_inheritedByPrivateModules() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          binder().requireAtInjectOnConstructors();
+          install(new PrivateModule() {
+            @Override
+            protected void configure() {
+              bind(NoCxtors.class);
+            }
+          });
+        }
+      });
+      fail();
+    } catch (CreationException ce) {
+      assertEquals(1, ce.getErrorMessages().size());
+      Asserts.assertContains(ce.getMessage(),
+          "1) Explicit @Inject annotations are required on constructors, but "
+          + NoCxtors.class.getName() + " has no constructors annotated with @Inject",
+          "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure");
+    }
+  }
+  
+  public void testNoCxtors_accumulatesAllErrors() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          bind(NoCxtors.class);
+          bind(AnotherNoCxtors.class);
+          binder().requireAtInjectOnConstructors();
+        }
+      });
+      fail();
+    } catch (CreationException ce) {
+      assertEquals(2, ce.getErrorMessages().size());
+      Asserts.assertContains(ce.getMessage(),
+          "1) Explicit @Inject annotations are required on constructors, but "
+          + NoCxtors.class.getName() + " has no constructors annotated with @Inject",
+          "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure",
+          "2) Explicit @Inject annotations are required on constructors, but "
+          + AnotherNoCxtors.class.getName() + " has no constructors annotated with @Inject",
+          "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure");
+    }
+  }
+  
+  public void testNoCxtors_separateOptionsForPrivateModules() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          bind(AnotherNoCxtors.class);
+          install(new PrivateModule() {
+            @Override
+            protected void configure() {
+              binder().requireAtInjectOnConstructors();
+              bind(NoCxtors.class);
+            }
+          });
+        }
+      });
+      fail();
+    } catch (CreationException ce) {
+      // This is testing that the parent module doesn't fail because it isn't included
+      // in the error message.
+      assertEquals(1, ce.getErrorMessages().size());
+      Asserts.assertContains(ce.getMessage(),
+          "1) Explicit @Inject annotations are required on constructors, but "
+          + NoCxtors.class.getName() + " has no constructors annotated with @Inject",
+          "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure");
+    }
+  }
+  
+  public void testManyConstructorsButNoneWithAtInject() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          bind(ManyConstructors.class);
+          binder().requireAtInjectOnConstructors();
+        }
+      });
+      fail();
+    } catch (CreationException ce) {
+      assertEquals(1, ce.getErrorMessages().size());
+      Asserts.assertContains(ce.getMessage(),
+          "1) Explicit @Inject annotations are required on constructors, but "
+          + ManyConstructors.class.getName() + " has no constructors annotated with @Inject",
+          "at " + RequireAtInjectOnConstructorsTest.class.getName() + "$", "configure");
+    }
+  }
+  
+  public void testRequireAtInjectStillAllowsToConstructorBindings() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          try {
+            bind(ManyConstructors.class)
+                .toConstructor(ManyConstructors.class.getDeclaredConstructor());
+          } catch (Exception e) {
+            throw new RuntimeException(e);
+          }
+          binder().requireAtInjectOnConstructors();
+        }
+      });
+    injector.getInstance(ManyConstructors.class);
+  }
+  
+  private static interface Interface {}
+  private static class NoCxtors implements Interface {}  
+  private static class AnotherNoCxtors {}
+  private static class ManyConstructors {
+    @SuppressWarnings("unused") ManyConstructors() {}
+    @SuppressWarnings("unused") ManyConstructors(String a) {}
+    @SuppressWarnings("unused") ManyConstructors(int a) {}
+  }
+}