Refinements to PrivateModules:
 - new test cases
 - support for @Provides methods
 - error detection on keys that are exposed but not bound

Also refactoring ProviderMethods to make this work.

I'm beginning to run into a wall with what is possible with implementing private modules as an extension rather than as a part of core. In particular:
 - I need to hack ProviderMethods so they're not installed in the public scope
 - I need to hack BindingProcessor to tolerate doubly-bound keys for Private Modules

The lack of core-support is also going to prevent private modules from having first-class support in the SPI. Ideally we should be able to have a binding type called "ChildBinding" to which the exposed keys belong. This binding would simply point at the child injector that implements the binding (and possibly the delegate binding also).

It's also preventing me from being able to detect a small class of errors - when a child module binds a key that's exposed by one of its sibling modules, we don't detect the binding conflict.


git-svn-id: https://google-guice.googlecode.com/svn/trunk@633 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/privatemodules/src/com/google/inject/privatemodules/Exposed.java b/extensions/privatemodules/src/com/google/inject/privatemodules/Exposed.java
new file mode 100644
index 0000000..264af8c
--- /dev/null
+++ b/extensions/privatemodules/src/com/google/inject/privatemodules/Exposed.java
@@ -0,0 +1,32 @@
+/**
+ * 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.privatemodules;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+import java.lang.annotation.Documented;
+
+/**
+ * Acccompanies a {@literal @}{@link com.google.inject.Provides Provides} method annotation in a
+ * private module to indicate that the provided binding is exposed.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+@Target(ElementType.METHOD) @Retention(RUNTIME) @Documented
+public @interface Exposed {}
diff --git a/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java b/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
index e6660b1..f469e41 100644
--- a/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
+++ b/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
@@ -20,27 +20,33 @@
 import static com.google.common.base.Preconditions.checkState;
 import com.google.common.collect.Sets;
 import com.google.inject.Binder;
+import com.google.inject.Binding;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.Module;
 import com.google.inject.Provider;
 import com.google.inject.Scope;
-import com.google.inject.Scopes;
 import com.google.inject.Stage;
 import com.google.inject.TypeLiteral;
 import com.google.inject.binder.AnnotatedBindingBuilder;
 import com.google.inject.binder.AnnotatedConstantBindingBuilder;
 import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.internal.ProviderMethod;
+import com.google.inject.internal.ProviderMethodsModule;
 import com.google.inject.internal.SourceProvider;
 import com.google.inject.internal.UniqueAnnotations;
 import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.DefaultElementVisitor;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.ElementVisitor;
 import com.google.inject.spi.Elements;
 import com.google.inject.spi.Message;
 import com.google.inject.spi.ModuleWriter;
 import com.google.inject.spi.TypeConverter;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
+import java.util.List;
 import java.util.Set;
 import org.aopalliance.intercept.MethodInterceptor;
 
@@ -69,15 +75,24 @@
     // otherwise we're being run for the private injector
     } else {
       checkState(this.privateBinder == null, "Re-entry is not allowed.");
-      this.privateBinder = binder.skipSources(PrivateModule.class);
+      privateBinder = binder.skipSources(PrivateModule.class);
       try {
         configurePrivateBindings();
 
+        ProviderMethodsModule providerMethodsModule = ProviderMethodsModule.forPrivateModule(this);
+        for (ProviderMethod<?> providerMethod
+            : providerMethodsModule.getProviderMethods(privateBinder)) {
+          providerMethod.configure(privateBinder);
+          if (providerMethod.getMethod().isAnnotationPresent(Exposed.class)) {
+            expose(providerMethod.getKey());
+          }
+        }
+
         for (Expose<?> expose : exposes) {
           expose.initPrivateProvider(binder);
         }
       } finally {
-        this.privateBinder = null;
+        privateBinder = null;
       }
     }
   }
@@ -87,10 +102,17 @@
     Key<Ready> readyKey = Key.get(Ready.class, UniqueAnnotations.create());
     readyProvider = publicBinder.getProvider(readyKey);
     try {
-      // gather elements and exposes from the private module by being re-entrant on configure()
-      final Module privateModule = new ModuleWriter().create(Elements.getElements(this));
+      List<Element> privateElements = Elements.getElements(this); // reentrant on configure()
+      Set<Key<?>> privatelyBoundKeys = getBoundKeys(privateElements);
+      final Module privateModule = new ModuleWriter().create(privateElements);
+
       for (Expose<?> expose : exposes) {
-        expose.configure(publicBinder);
+        if (!privatelyBoundKeys.contains(expose.key)) {
+          publicBinder.addError("Could not expose() at %s%n %s must be explicitly bound.", 
+              expose.source, expose.key);
+        } else {
+          expose.configure(publicBinder);
+        }
       }
 
       // create the private injector while the public injector is injecting its members
@@ -101,7 +123,7 @@
           publicInjector.createChildInjector(privateModule);
           return new Ready();
         }
-      }).in(Scopes.SINGLETON);
+      }).asEagerSingleton();
 
     } finally {
       readyProvider = null;
@@ -113,19 +135,19 @@
 
   public abstract void configurePrivateBindings();
 
-  protected <T> void expose(Key<T> key) {
+  protected final <T> void expose(Key<T> key) {
     checkState(exposes != null, "Cannot expose %s, private module is not ready");
     exposes.add(new Expose<T>(sourceProvider.get(), readyProvider, key));
   }
 
-  protected <T> ExposedKeyBuilder expose(Class<T> type) {
+  protected final <T> ExposedKeyBuilder expose(Class<T> type) {
     checkState(exposes != null, "Cannot expose %s, private module is not ready");
     Expose<T> expose = new Expose<T>(sourceProvider.get(), readyProvider, Key.get(type));
     exposes.add(expose);
     return expose;
   }
 
-  protected <T> ExposedKeyBuilder expose(TypeLiteral<T> type) {
+  protected final <T> ExposedKeyBuilder expose(TypeLiteral<T> type) {
     checkState(exposes != null, "Cannot expose %s, private module is not ready");
     Expose<T> expose = new Expose<T>(sourceProvider.get(), readyProvider, Key.get(type));
     exposes.add(expose);
@@ -164,7 +186,7 @@
 
     /** Sets the provider in the private injector, to be used by the public injector */
     private void initPrivateProvider(Binder privateBinder) {
-      privateProvider = privateBinder.getProvider(key);
+      privateProvider = privateBinder.withSource(source).getProvider(key);
     }
 
     /** Creates a binding in the public binder */
@@ -178,83 +200,102 @@
     }
   }
 
+  /**
+   * Returns the set of keys bound by {@code elements}.
+   */
+  private Set<Key<?>> getBoundKeys(Iterable<? extends Element> elements) {
+    final Set<Key<?>> privatelyBoundKeys = Sets.newHashSet();
+    ElementVisitor<Void> visitor = new DefaultElementVisitor<Void>() {
+      public <T> Void visitBinding(Binding<T> command) {
+        privatelyBoundKeys.add(command.getKey());
+        return null;
+      }
+    };
+
+    for (Element element : elements) {
+      element.acceptVisitor(visitor);
+    }
+
+    return privatelyBoundKeys;
+  }
+
   // everything below is copied from AbstractModule
 
-  protected Binder binder() {
+  protected final Binder binder() {
     return privateBinder;
   }
 
-  protected void bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope) {
+  protected final void bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope) {
     privateBinder.bindScope(scopeAnnotation, scope);
   }
 
-  protected <T> LinkedBindingBuilder<T> bind(Key<T> key) {
+  protected final <T> LinkedBindingBuilder<T> bind(Key<T> key) {
     return privateBinder.bind(key);
   }
 
-  protected <T> AnnotatedBindingBuilder<T> bind(TypeLiteral<T> typeLiteral) {
+  protected final <T> AnnotatedBindingBuilder<T> bind(TypeLiteral<T> typeLiteral) {
     return privateBinder.bind(typeLiteral);
   }
 
-  protected <T> AnnotatedBindingBuilder<T> bind(Class<T> clazz) {
+  protected final <T> AnnotatedBindingBuilder<T> bind(Class<T> clazz) {
     return privateBinder.bind(clazz);
   }
 
-  protected AnnotatedConstantBindingBuilder bindConstant() {
+  protected final AnnotatedConstantBindingBuilder bindConstant() {
     return privateBinder.bindConstant();
   }
 
-  protected void install(Module module) {
+  protected final void install(Module module) {
     privateBinder.install(module);
   }
 
-  protected void addError(String message, Object... arguments) {
+  protected final void addError(String message, Object... arguments) {
     privateBinder.addError(message, arguments);
   }
 
-  protected void addError(Throwable t) {
+  protected final void addError(Throwable t) {
     privateBinder.addError(t);
   }
 
-  protected void addError(Message message) {
+  protected final void addError(Message message) {
     privateBinder.addError(message);
   }
 
-  protected void requestInjection(Object... objects) {
+  protected final void requestInjection(Object... objects) {
     privateBinder.requestInjection(objects);
   }
 
-  protected void requestStaticInjection(Class<?>... types) {
+  protected final void requestStaticInjection(Class<?>... types) {
     privateBinder.requestStaticInjection(types);
   }
 
-  protected void bindInterceptor(Matcher<? super Class<?>> classMatcher,
+  protected final void bindInterceptor(Matcher<? super Class<?>> classMatcher,
       Matcher<? super Method> methodMatcher, MethodInterceptor... interceptors) {
     privateBinder.bindInterceptor(classMatcher, methodMatcher, interceptors);
   }
 
-  protected void requireBinding(Key<?> key) {
+  protected final void requireBinding(Key<?> key) {
     privateBinder.getProvider(key);
   }
 
-  protected void requireBinding(Class<?> type) {
+  protected final void requireBinding(Class<?> type) {
     privateBinder.getProvider(type);
   }
 
-  protected <T> Provider<T> getProvider(Key<T> key) {
+  protected final <T> Provider<T> getProvider(Key<T> key) {
     return privateBinder.getProvider(key);
   }
 
-  protected <T> Provider<T> getProvider(Class<T> type) {
+  protected final <T> Provider<T> getProvider(Class<T> type) {
     return privateBinder.getProvider(type);
   }
 
-  protected void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
+  protected final void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
       TypeConverter converter) {
     privateBinder.convertToTypes(typeMatcher, converter);
   }
 
-  protected Stage currentStage() {
+  protected final Stage currentStage() {
     return privateBinder.currentStage();
   }
 }
diff --git a/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java b/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
index 913f7a8..3fcf1d4 100644
--- a/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
+++ b/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
@@ -17,10 +17,13 @@
 package com.google.inject.privatemodules;
 
 import com.google.inject.AbstractModule;
+import static com.google.inject.Asserts.assertContains;
+import com.google.inject.CreationException;
 import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Key;
+import com.google.inject.Provides;
 import com.google.inject.name.Named;
 import static com.google.inject.name.Names.named;
 import junit.framework.TestCase;
@@ -64,6 +67,120 @@
     assertEquals("ii", ab2.b);
   }
 
+  public void testPrivateModulesAndProvidesMethods() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        install(new PrivateModule() {
+          public void configurePrivateBindings() {
+            expose(String.class).annotatedWith(named("a"));
+          }
+
+          @Provides @Named("a") String providePublicA() {
+            return "i";
+          }
+
+          @Provides @Named("b") String providePrivateB() {
+            return "private";
+          }
+        });
+
+        install(new PrivateModule() {
+          public void configurePrivateBindings() {}
+
+          @Provides @Named("a") String providePrivateA() {
+            return "private";
+          }
+
+          @Provides @Exposed @Named("b") String providePublicB() {
+            return "ii";
+          }
+        });
+      }
+    });
+
+    assertEquals("i", injector.getInstance(Key.get(String.class, named("a"))));
+    assertEquals("ii", injector.getInstance(Key.get(String.class, named("b"))));
+  }
+
+  public void testCannotBindAKeyExportedByASibling() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        protected void configure() {
+          install(new PrivateModule() {
+            public void configurePrivateBindings() {
+              bind(String.class).toInstance("public");
+              expose(String.class);
+            }
+          });
+
+          install(new PrivateModule() {
+            public void configurePrivateBindings() {
+              bind(String.class).toInstance("private");
+            }
+          });
+        }
+      });
+      fail("KNOWN ISSUE: Binding to 'private' should conflict with binding to 'public'");
+    } catch (CreationException expected) {
+      assertContains(expected.getMessage(), "Cannot bind String");
+    }
+  }
+
+  public void testExposeButNoBind() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        protected void configure() {
+          bind(String.class).annotatedWith(named("a")).toInstance("a");
+          bind(String.class).annotatedWith(named("b")).toInstance("b");
+
+          install(new PrivateModule() {
+            public void configurePrivateBindings() {
+              expose(AB.class);
+            }
+          });
+        }
+      });
+      fail("AB was exposed but not bound");
+    } catch (CreationException expected) {
+      assertContains(expected.getMessage(), "Could not expose() at ",
+          PrivateModuleTest.class.getName(), ".configurePrivateBindings(PrivateModuleTest.java:",
+          Key.get(AB.class).toString(), " must be explicitly bound.");
+    }
+  }
+
+  public void testNestedPrivateInjectors() {
+    Injector injector = Guice.createInjector(new PrivateModule() {
+      public void configurePrivateBindings() {
+        expose(String.class);
+
+        install(new PrivateModule() {
+          public void configurePrivateBindings() {
+            bind(String.class).toInstance("nested");
+            expose(String.class);
+          }
+        });
+      }
+    });
+
+    assertEquals("nested", injector.getInstance(String.class));
+  }
+
+  public void testInstallingRegularModulesFromPrivateModules() {
+    Injector injector = Guice.createInjector(new PrivateModule() {
+      public void configurePrivateBindings() {
+        expose(String.class);
+
+        install(new AbstractModule() {
+          protected void configure() {
+            bind(String.class).toInstance("nested");
+          }
+        });
+      }
+    });
+
+    assertEquals("nested", injector.getInstance(String.class));
+  }
+
   static class AB {
     @Inject @Named("a") String a;
     @Inject @Named("b") String b;
diff --git a/src/com/google/inject/BindingProcessor.java b/src/com/google/inject/BindingProcessor.java
index 18d1617..78ad9f2 100644
--- a/src/com/google/inject/BindingProcessor.java
+++ b/src/com/google/inject/BindingProcessor.java
@@ -39,11 +39,11 @@
  */
 class BindingProcessor extends AbstractProcessor {
 
-  private BindingTargetVisitor<Object, Object> GET_BINDING_PROVIDER
-      = new DefaultBindingTargetVisitor<Object, Object>() {
-    public Object visitProvider(
-        Provider<?> provider, Set<InjectionPoint> injectionPoints) {
-      return provider;
+  /** Returns the class name of the bound provider, or null */
+  private BindingTargetVisitor<Object, String> GET_BOUND_PROVIDER_CLASS_NAME
+      = new DefaultBindingTargetVisitor<Object, String>() {
+    public String visitProvider(Provider<?> provider, Set<InjectionPoint> injectionPoints) {
+      return provider.getClass().getName();
     }
   };
 
@@ -257,8 +257,8 @@
 
     Binding<?> original = state.getExplicitBinding(key);
     if (original != null && !"com.google.inject.privatemodules.PrivateModule$Expose"
-        .equals(original.acceptTargetVisitor(GET_BINDING_PROVIDER).getClass().getName())) {
-      // the hard-coded class name is certainly lame, but it avoids an even lamer dependency... 
+        .equals(original.acceptTargetVisitor(GET_BOUND_PROVIDER_CLASS_NAME))) {
+      // the hard-coded class name is certainly lame, but it avoids an even lamer dependency...
       errors.bindingAlreadySet(key, original.getSource());
       return;
     }
@@ -277,12 +277,13 @@
       AbstractModule.class,
       Binder.class,
       Binding.class,
+      Injector.class,
       Key.class,
       Module.class,
       Provider.class, 
       Scope.class,
       TypeLiteral.class);
-  // TODO(jessewilson): fix BuiltInModule, then add Injector and Stage
+  // TODO(jessewilson): fix BuiltInModule, then add Stage
 
   interface CreationListener {
     void notify(InjectorImpl injector, Errors errors);
diff --git a/src/com/google/inject/internal/Errors.java b/src/com/google/inject/internal/Errors.java
index e3a3cb3..e3689b7 100644
--- a/src/com/google/inject/internal/Errors.java
+++ b/src/com/google/inject/internal/Errors.java
@@ -111,7 +111,7 @@
       TypeLiteral<?> type, MatcherAndConverter matchingConverter) {
     return addMessage("Received null converting '%s' (bound at %s) to %s%n"
         + " using %s.",
-        stringValue, source, type, matchingConverter);
+        stringValue, sourceToString(source), type, matchingConverter);
   }
 
   public Errors conversionTypeError(String stringValue, Object source, TypeLiteral<?> type,
@@ -119,7 +119,7 @@
     return addMessage("Type mismatch converting '%s' (bound at %s) to %s%n"
         + " using %s.%n"
         + " Converter returned %s.",
-        stringValue, source, type, matchingConverter, converted);
+        stringValue, sourceToString(source), type, matchingConverter, converted);
   }
 
   public Errors conversionError(String stringValue, Object source,
@@ -127,7 +127,7 @@
     return addMessage(cause, "Error converting '%s' (bound at %s) to %s%n" 
         + " using %s.%n"
         + " Reason: %s",
-        stringValue, source, type, matchingConverter, cause);
+        stringValue, sourceToString(source), type, matchingConverter, cause);
   }
 
   public Errors ambiguousTypeConversion(String stringValue, Object source, TypeLiteral<?> type,
@@ -136,7 +136,7 @@
         + " %s and%n"
         + " %s.%n"
         + " Please adjust your type converter configuration to avoid overlapping matches.",
-        stringValue, source, type, a, b);
+        stringValue, sourceToString(source), type, a, b);
   }
 
   public Errors bindingToProvider() {
@@ -162,7 +162,7 @@
 
   public Errors missingRuntimeRetention(Object source) {
     return addMessage("Please annotate with @Retention(RUNTIME).%n"
-        + " Bound at %s.", source);
+        + " Bound at %s.", sourceToString(source));
   }
 
   public Errors missingScopeAnnotation() {
@@ -185,7 +185,7 @@
   public Errors scopeAnnotationOnAbstractType(
       Class<? extends Annotation> scopeAnnotation, Class<?> type, Object source) {
     return addMessage("%s is annotated with %s, but scope annotations are not supported "
-        + "for abstract types.%n Bound at %s.", type, scopeAnnotation, source);
+        + "for abstract types.%n Bound at %s.", type, scopeAnnotation, sourceToString(source));
   }
 
   public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) {
@@ -238,7 +238,7 @@
   }
 
   public Errors bindingAlreadySet(Key<?> key, Object source) {
-    return addMessage("A binding to %s was already configured at %s.", key, source);
+    return addMessage("A binding to %s was already configured at %s.", key, sourceToString(source));
   }
 
   public Errors childBindingAlreadySet(Key<?> key) {
diff --git a/src/com/google/inject/internal/ProviderMethod.java b/src/com/google/inject/internal/ProviderMethod.java
new file mode 100644
index 0000000..a371f8a
--- /dev/null
+++ b/src/com/google/inject/internal/ProviderMethod.java
@@ -0,0 +1,88 @@
+/**
+ * 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.internal;
+
+import com.google.inject.Provider;
+import com.google.inject.Key;
+import com.google.inject.Binder;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+/**
+ * A provider that invokes a method and returns its result.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class ProviderMethod<T> implements Provider<T> {
+  // TODO: this should be a top-level implementation of Binding
+
+  private final Key<T> key;
+  private final Class<? extends Annotation> scopeAnnotation;
+  private final Object instance;
+  private final Method method;
+  private final List<Provider<?>> parameterProviders;
+
+  /**
+   * @param method the method to invoke. It's return type must be the same type as {@code key}.
+   */
+  ProviderMethod(Key<T> key, Method method, Object instance,
+      List<Provider<?>> parameterProviders, Class<? extends Annotation> scopeAnnotation) {
+    this.key = key;
+    this.scopeAnnotation = scopeAnnotation;
+    this.instance = instance;
+    this.method = method;
+    this.parameterProviders = parameterProviders;
+
+    method.setAccessible(true);
+  }
+
+  public Key<T> getKey() {
+    return key;
+  }
+
+  public Method getMethod() {
+    return method;
+  }
+
+  public void configure(Binder binder) {
+    if (scopeAnnotation != null) {
+      binder.bind(key).toProvider(this).in(scopeAnnotation);
+    } else {
+      binder.bind(key).toProvider(this);
+    }
+  }
+
+  public T get() {
+    Object[] parameters = new Object[parameterProviders.size()];
+    for (int i = 0; i < parameters.length; i++) {
+      parameters[i] = parameterProviders.get(i).get();
+    }
+
+    try {
+      // We know this cast is safe becase T is the method's return type.
+      @SuppressWarnings({ "unchecked", "UnnecessaryLocalVariable" })
+      T result = (T) method.invoke(instance, parameters);
+      return result;
+    } catch (IllegalAccessException e) {
+      throw new AssertionError(e);
+    } catch (InvocationTargetException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/src/com/google/inject/internal/ProviderMethodsModule.java b/src/com/google/inject/internal/ProviderMethodsModule.java
new file mode 100644
index 0000000..d102d63
--- /dev/null
+++ b/src/com/google/inject/internal/ProviderMethodsModule.java
@@ -0,0 +1,136 @@
+/**
+ * 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.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.Lists;
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.spi.Message;
+import com.google.inject.util.Modules;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Creates bindings to methods annotated with {@literal @}{@link Provides}. Use the scope and
+ * binding annotations on the provider method to configure the binding.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public final class ProviderMethodsModule implements Module {
+  private final Module delegate;
+  private final TypeResolver typeResolver;
+
+  private ProviderMethodsModule(Module delegate) {
+    this.delegate = checkNotNull(delegate, "delegate");
+    this.typeResolver = new TypeResolver(this.delegate.getClass());
+  }
+
+  /**
+   * Returns a module which creates bindings for provider methods from the given module.
+   */
+  public static Module forModule(Module module) {
+    // avoid infinite recursion, since installing a module always installs itself
+    if (module instanceof ProviderMethodsModule) {
+      return Modules.EMPTY_MODULE;
+    }
+
+    // don't install provider methods for private modules, they take care of that manually
+    for (Class<?> c = module.getClass(); c != Object.class; c = c.getSuperclass()) {
+      // use the ugly class name to avoid an even uglier dependency. If private modules ever get
+      // incorporated into core, we could use a single instanceof instead of this loop
+      if (c.getName().equals("com.google.inject.privatemodules.PrivateModule")) {
+        return Modules.EMPTY_MODULE;
+      }
+    }
+
+    return new ProviderMethodsModule(module);
+  }
+
+  /** See {@link com.google.inject.privatemodules.PrivateModule}. */
+  public static ProviderMethodsModule forPrivateModule(Module privateModule) {
+    return new ProviderMethodsModule(privateModule);
+  }
+
+  public synchronized void configure(Binder binder) {
+    for (ProviderMethod<?> providerMethod : getProviderMethods(binder)) {
+      providerMethod.configure(binder);
+    }
+  }
+
+  public List<ProviderMethod<?>> getProviderMethods(Binder binder) {
+    List<ProviderMethod<?>> result = Lists.newArrayList();
+    for (Class c = delegate.getClass(); c != Object.class; c = c.getSuperclass()) {
+      for (Method method : c.getDeclaredMethods()) {
+        if (method.isAnnotationPresent(Provides.class)) {
+          result.add(createProviderMethod(binder, method));
+        }
+      }
+    }
+    return result;
+  }
+
+  <T> ProviderMethod<T> createProviderMethod(Binder binder, final Method method) {
+    binder = binder.withSource(method);
+    Errors errors = new Errors(method);
+
+    // prepare the parameter providers
+    List<Provider<?>> parameterProviders = Lists.newArrayList();
+    List<Type> parameterTypes = typeResolver.getParameterTypes(method);
+    Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+    for (int i = 0; i < parameterTypes.size(); i++) {
+      Key<?> key = getKey(errors, TypeLiteral.get(parameterTypes.get(i)),
+          method, parameterAnnotations[i]);
+      parameterProviders.add(binder.getProvider(key));
+    }
+
+    // Define T as the method's return type.
+    @SuppressWarnings("unchecked") TypeLiteral<T> returnType
+        = (TypeLiteral<T>) TypeLiteral.get(typeResolver.getReturnType(method));
+
+    Key<T> key = getKey(errors, returnType, method, method.getAnnotations());
+    Class<? extends Annotation> scopeAnnotation
+        = Annotations.findScopeAnnotation(errors, method.getAnnotations());
+
+    for (Message message : errors.getMessages()) {
+      binder.addError(message);
+    }
+
+    return new ProviderMethod<T>(key, method, delegate, parameterProviders, scopeAnnotation);
+  }
+
+  <T> Key<T> getKey(Errors errors, TypeLiteral<T> type, Member member, Annotation[] annotations) {
+    Annotation bindingAnnotation = Annotations.findBindingAnnotation(errors, member, annotations);
+    return bindingAnnotation == null ? Key.get(type) : Key.get(type, bindingAnnotation);
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof ProviderMethodsModule
+        && ((ProviderMethodsModule) o).delegate == delegate;
+  }
+
+  @Override public int hashCode() {
+    return delegate.hashCode();
+  }
+}
diff --git a/src/com/google/inject/spi/Elements.java b/src/com/google/inject/spi/Elements.java
index 025674c..1b66e7a 100644
--- a/src/com/google/inject/spi/Elements.java
+++ b/src/com/google/inject/spi/Elements.java
@@ -32,6 +32,7 @@
 import com.google.inject.binder.AnnotatedBindingBuilder;
 import com.google.inject.binder.AnnotatedConstantBindingBuilder;
 import com.google.inject.internal.ModuleBinding;
+import com.google.inject.internal.ProviderMethodsModule;
 import com.google.inject.internal.SourceProvider;
 import com.google.inject.matcher.Matcher;
 import java.lang.annotation.Annotation;
@@ -157,7 +158,7 @@
         } catch (RuntimeException e) {
           addError(e);
         }
-        install(ProviderMethods.from(module));
+        install(ProviderMethodsModule.forModule(module));
       }
     }