Author: limpbizkit
Date: Wed Oct 15 14:25:50 2008
New Revision: 633
Added:
trunk/extensions/privatemodules/src/com/google/inject/privatemodules/Exposed.java
trunk/src/com/google/inject/internal/ProviderMethod.java
trunk/src/com/google/inject/internal/ProviderMethodsModule.java
Modified:
trunk/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
trunk/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
trunk/src/com/google/inject/BindingProcessor.java
trunk/src/com/google/inject/internal/Errors.java
trunk/src/com/google/inject/spi/Elements.java
Log:
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.
Added:
trunk/extensions/privatemodules/src/com/google/inject/privatemodules/Exposed.java
==============================================================================
--- (empty file)
+++
trunk/extensions/privatemodules/src/com/google/inject/privatemodules/Exposed.java
Wed Oct 15 14:25:50 2008
@@ -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 [EMAIL PROTECTED] @[EMAIL PROTECTED]
com.google.inject.Provides Provides}
method annotation in a
+ * private module to indicate that the provided binding is exposed.
+ *
+ * @author [EMAIL PROTECTED] (Jesse Wilson)
+ */
[EMAIL PROTECTED](ElementType.METHOD) @Retention(RUNTIME) @Documented
+public @interface Exposed {}
Modified:
trunk/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
==============================================================================
---
trunk/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
(original)
+++
trunk/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
Wed Oct 15 14:25:50 2008
@@ -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 [EMAIL PROTECTED] 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();
}
}
Modified:
trunk/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
==============================================================================
---
trunk/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
(original)
+++
trunk/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
Wed Oct 15 14:25:50 2008
@@ -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;
@@ -62,6 +65,120 @@
AB ab2 = injector.getInstance(Key.get(AB.class, named("two")));
assertEquals("public", ab2.a);
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 {
Modified: trunk/src/com/google/inject/BindingProcessor.java
==============================================================================
--- trunk/src/com/google/inject/BindingProcessor.java (original)
+++ trunk/src/com/google/inject/BindingProcessor.java Wed Oct 15 14:25:50
2008
@@ -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);
Modified: trunk/src/com/google/inject/internal/Errors.java
==============================================================================
--- trunk/src/com/google/inject/internal/Errors.java (original)
+++ trunk/src/com/google/inject/internal/Errors.java Wed Oct 15 14:25:50
2008
@@ -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) {
Added: trunk/src/com/google/inject/internal/ProviderMethod.java
==============================================================================
--- (empty file)
+++ trunk/src/com/google/inject/internal/ProviderMethod.java Wed Oct 15
14:25:50 2008
@@ -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 [EMAIL PROTECTED] (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 [EMAIL PROTECTED] 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);
+ }
+ }
+}
Added: trunk/src/com/google/inject/internal/ProviderMethodsModule.java
==============================================================================
--- (empty file)
+++ trunk/src/com/google/inject/internal/ProviderMethodsModule.java Wed Oct
15 14:25:50 2008
@@ -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 [EMAIL PROTECTED] @[EMAIL
PROTECTED]
Provides}. Use the scope and
+ * binding annotations on the provider method to configure the binding.
+ *
+ * @author [EMAIL PROTECTED] (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 [EMAIL PROTECTED] 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();
+ }
+}
Modified: trunk/src/com/google/inject/spi/Elements.java
==============================================================================
--- trunk/src/com/google/inject/spi/Elements.java (original)
+++ trunk/src/com/google/inject/spi/Elements.java Wed Oct 15 14:25:50 2008
@@ -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));
}
}
--~--~---------~--~----~------------~-------~--~----~
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
-~----------~----~----~----~------~----~------~--~---