aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsameb <sameb@google.com>2015-02-02 12:45:25 -0800
committerSam Berlin <sameb@google.com>2015-02-03 18:07:07 -0500
commit9867f9c2142355ae958f9eeb8fb96811082c8812 (patch)
treefb4ee51e5e4bfead67e2951c74958cdfe8b29194
parentf11bf2d26ccd07e72805b789b33d855e223b1ed7 (diff)
downloadguice-9867f9c2142355ae958f9eeb8fb96811082c8812.tar.gz
Implement my old patch for issue #366, with some tweaks. This does the
following: * Fixes @Provides injection so that parameters are checked for nullability. By default this will error. The flag is named: guice_check_nullable_provides_params and can be set to ERROR, WARNING or IGNORE. * Adds InjectionPoint.forMethod to build an InjectionPoint off an arbitrary method. * Adds Binder.getProvider(Dependency) to a get a Provider for a given dependency (with all its nullability & injection points maintained). * Update ProviderLookup to accept a Dependency in addition to a Key. This is in preparation for two things: 1) Allowing multibindings/mapbindings/optionalbindings to be specified as annotations on methods in a module. 2) Adding a dagger compatibility module. ... the general idea will be that I'll also add a hook into ProvidesMethodModule somehow to look at arbitrary other annotations and let folks process them specially. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=85353820
-rw-r--r--core/src/com/google/inject/Binder.java13
-rw-r--r--core/src/com/google/inject/internal/Errors.java29
-rw-r--r--core/src/com/google/inject/internal/InjectorImpl.java6
-rw-r--r--core/src/com/google/inject/internal/InternalFlags.java21
-rw-r--r--core/src/com/google/inject/internal/LookupProcessor.java2
-rw-r--r--core/src/com/google/inject/internal/ProviderMethodsModule.java38
-rw-r--r--core/src/com/google/inject/spi/Elements.java6
-rw-r--r--core/src/com/google/inject/spi/InjectionPoint.java14
-rw-r--r--core/src/com/google/inject/spi/ProviderLookup.java23
-rw-r--r--core/test/com/google/inject/spi/ProviderMethodsTest.java195
10 files changed, 261 insertions, 86 deletions
diff --git a/core/src/com/google/inject/Binder.java b/core/src/com/google/inject/Binder.java
index 91f436f6..e8957593 100644
--- a/core/src/com/google/inject/Binder.java
+++ b/core/src/com/google/inject/Binder.java
@@ -20,6 +20,7 @@ import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.AnnotatedConstantBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.Dependency;
import com.google.inject.spi.Message;
import com.google.inject.spi.ProvisionListener;
import com.google.inject.spi.TypeConverter;
@@ -323,6 +324,18 @@ public interface Binder {
<T> Provider<T> getProvider(Key<T> key);
/**
+ * Returns the provider used to obtain instances for the given injection key.
+ * The returned provider will be attached to the injection point and will
+ * follow the nullability specified in the dependency.
+ * Additionally, the returned provider will not be valid until the {@link Injector}
+ * has been created. The provider will throw an {@code IllegalStateException} if you
+ * try to use it beforehand.
+ *
+ * @since 4.0
+ */
+ <T> Provider<T> getProvider(Dependency<T> dependency);
+
+ /**
* Returns the provider used to obtain instances for the given injection type.
* The returned provider will not be valid until the {@link Injector} has been
* created. The provider will throw an {@code IllegalStateException} if you
diff --git a/core/src/com/google/inject/internal/Errors.java b/core/src/com/google/inject/internal/Errors.java
index 385d4b82..87e0cc87 100644
--- a/core/src/com/google/inject/internal/Errors.java
+++ b/core/src/com/google/inject/internal/Errors.java
@@ -22,9 +22,11 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.inject.ConfigurationException;
import com.google.inject.CreationException;
+import com.google.inject.Guice;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.Provider;
+import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.Scope;
import com.google.inject.TypeLiteral;
@@ -53,6 +55,8 @@ import java.util.Collection;
import java.util.Formatter;
import java.util.List;
import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* A collection of error messages. If this type is passed as a method parameter, the method is
@@ -71,6 +75,8 @@ import java.util.Set;
*/
public final class Errors implements Serializable {
+ private static final Logger logger = Logger.getLogger(Guice.class.getName());
+
/**
* The root errors object. Used to access the list of error messages.
@@ -602,6 +608,29 @@ public final class Errors implements Serializable {
return value;
}
+ // Hack to allow null parameters to @Provides methods, for backwards compatibility.
+ if (dependency.getInjectionPoint().getMember() instanceof Method) {
+ Method annotated = (Method) dependency.getInjectionPoint().getMember();
+ if (annotated.isAnnotationPresent(Provides.class)) {
+ switch (InternalFlags.getNullableProvidesOption()) {
+ case ERROR:
+ break; // break out & let the below exception happen
+ case IGNORE:
+ return value; // user doesn't care about injecting nulls to non-@Nullables.
+ case WARN:
+ logger.log(Level.WARNING,
+ "Guice injected null into parameter {0} of {1} (a {2}), please mark it @Nullable."
+ + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an"
+ + " error.",
+ new Object[] {
+ dependency.getParameterIndex(),
+ convert(dependency.getInjectionPoint().getMember()),
+ convert(dependency.getKey())});
+ return null; // log & exit.
+ }
+ }
+ }
+
int parameterIndex = dependency.getParameterIndex();
String parameterName = (parameterIndex != -1)
? "parameter " + parameterIndex + " of "
diff --git a/core/src/com/google/inject/internal/InjectorImpl.java b/core/src/com/google/inject/internal/InjectorImpl.java
index eef7d98e..d1d028f1 100644
--- a/core/src/com/google/inject/internal/InjectorImpl.java
+++ b/core/src/com/google/inject/internal/InjectorImpl.java
@@ -1000,9 +1000,9 @@ final class InjectorImpl implements Injector, Lookups {
return getProvider(Key.get(type));
}
- <T> Provider<T> getProviderOrThrow(final Key<T> key, Errors errors) throws ErrorsException {
+ <T> Provider<T> getProviderOrThrow(final Dependency<T> dependency, Errors errors) throws ErrorsException {
+ final Key<T> key = dependency.getKey();
final BindingImpl<? extends T> binding = getBindingOrThrow(key, errors, JitLimitation.NO_JIT);
- final Dependency<T> dependency = Dependency.get(key);
return new Provider<T>() {
public T get() {
@@ -1034,7 +1034,7 @@ final class InjectorImpl implements Injector, Lookups {
public <T> Provider<T> getProvider(final Key<T> key) {
Errors errors = new Errors(key);
try {
- Provider<T> result = getProviderOrThrow(key, errors);
+ Provider<T> result = getProviderOrThrow(Dependency.get(key), errors);
errors.throwIfNewErrors(0);
return result;
} catch (ErrorsException e) {
diff --git a/core/src/com/google/inject/internal/InternalFlags.java b/core/src/com/google/inject/internal/InternalFlags.java
index 4e0b227e..85c07acb 100644
--- a/core/src/com/google/inject/internal/InternalFlags.java
+++ b/core/src/com/google/inject/internal/InternalFlags.java
@@ -33,6 +33,9 @@ public class InternalFlags {
private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING
= parseCustomClassLoadingOption();
+ private static final NullableProvidesOption NULLABLE_PROVIDES
+ = parseNullableProvidesOption(NullableProvidesOption.ERROR);
+
/**
* The options for Guice stack trace collection.
@@ -56,6 +59,15 @@ public class InternalFlags {
BRIDGE
}
+ public enum NullableProvidesOption {
+ /** Ignore null parameters to @Provides methods. */
+ IGNORE,
+ /** Warn if null parameters are passed to non-@Nullable parameters of provides methods. */
+ WARN,
+ /** Error if null parameters are passed to non-@Nullable parameters of provides parameters */
+ ERROR
+ }
+
public static IncludeStackTraceOption getIncludeStackTraceOption() {
return INCLUDE_STACK_TRACES;
}
@@ -64,6 +76,10 @@ public class InternalFlags {
return CUSTOM_CLASS_LOADING;
}
+ public static NullableProvidesOption getNullableProvidesOption() {
+ return NULLABLE_PROVIDES;
+ }
+
private static IncludeStackTraceOption parseIncludeStackTraceOption() {
return getSystemOption("guice_include_stack_traces",
IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE);
@@ -74,6 +90,11 @@ public class InternalFlags {
CustomClassLoadingOption.BRIDGE, CustomClassLoadingOption.OFF);
}
+ private static NullableProvidesOption parseNullableProvidesOption(
+ NullableProvidesOption defaultValue) {
+ return getSystemOption("guice_check_nullable_provides_params", defaultValue);
+ }
+
/**
* Gets the system option indicated by the specified key; runs as a privileged action.
*
diff --git a/core/src/com/google/inject/internal/LookupProcessor.java b/core/src/com/google/inject/internal/LookupProcessor.java
index 39718189..bf11b833 100644
--- a/core/src/com/google/inject/internal/LookupProcessor.java
+++ b/core/src/com/google/inject/internal/LookupProcessor.java
@@ -48,7 +48,7 @@ final class LookupProcessor extends AbstractProcessor {
@Override public <T> Boolean visit(ProviderLookup<T> lookup) {
// ensure the provider can be created
try {
- Provider<T> provider = injector.getProviderOrThrow(lookup.getKey(), errors);
+ Provider<T> provider = injector.getProviderOrThrow(lookup.getDependency(), errors);
lookup.initializeDelegate(provider);
} catch (ErrorsException e) {
errors.merge(e.getErrors()); // TODO: source
diff --git a/core/src/com/google/inject/internal/ProviderMethodsModule.java b/core/src/com/google/inject/internal/ProviderMethodsModule.java
index 214039d0..77a8ff28 100644
--- a/core/src/com/google/inject/internal/ProviderMethodsModule.java
+++ b/core/src/com/google/inject/internal/ProviderMethodsModule.java
@@ -29,6 +29,7 @@ import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Dependency;
+import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.Message;
import com.google.inject.util.Modules;
@@ -38,7 +39,6 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
-import java.util.logging.Logger;
/**
* Creates bindings to methods annotated with {@literal @}{@link Provides}. Use the scope and
@@ -48,7 +48,6 @@ import java.util.logging.Logger;
* @author jessewilson@google.com (Jesse Wilson)
*/
public final class ProviderMethodsModule implements Module {
- private static final Key<Logger> LOGGER_KEY = Key.get(Logger.class);
private final Object delegate;
private final TypeLiteral<?> typeLiteral;
@@ -202,35 +201,21 @@ public final class ProviderMethodsModule implements Module {
Errors errors = new Errors(method);
// prepare the parameter providers
- List<Dependency<?>> dependencies = Lists.newArrayList();
+ InjectionPoint point = InjectionPoint.forMethod(method, typeLiteral);
+ List<Dependency<?>> dependencies = point.getDependencies();
List<Provider<?>> parameterProviders = Lists.newArrayList();
- List<TypeLiteral<?>> parameterTypes = typeLiteral.getParameterTypes(method);
- Annotation[][] parameterAnnotations = method.getParameterAnnotations();
- for (int i = 0; i < parameterTypes.size(); i++) {
- Key<?> key = getKey(errors, parameterTypes.get(i), method, parameterAnnotations[i]);
- if (key.equals(LOGGER_KEY)) {
- // If it was a Logger, change the key to be unique & bind it to a
- // provider that provides a logger with a proper name.
- // This solves issue 482 (returning a new anonymous logger on every call exhausts memory)
- Key<Logger> loggerKey = Key.get(Logger.class, UniqueAnnotations.create());
- binder.bind(loggerKey).toProvider(new LogProvider(method));
- key = loggerKey;
- }
- dependencies.add(Dependency.get(key));
- parameterProviders.add(binder.getProvider(key));
+ for (Dependency<?> dependency : point.getDependencies()) {
+ parameterProviders.add(binder.getProvider(dependency));
}
@SuppressWarnings("unchecked") // Define T as the method's return type.
TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.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 ProviderMethod.create(key, method, delegate, ImmutableSet.copyOf(dependencies),
parameterProviders, scopeAnnotation, skipFastClassGeneration);
}
@@ -248,17 +233,4 @@ public final class ProviderMethodsModule implements Module {
@Override public int hashCode() {
return delegate.hashCode();
}
-
- /** A provider that returns a logger based on the method name. */
- private static final class LogProvider implements Provider<Logger> {
- private final String name;
-
- public LogProvider(Method method) {
- this.name = method.getDeclaringClass().getName() + "." + method.getName();
- }
-
- public Logger get() {
- return Logger.getLogger(name);
- }
- }
}
diff --git a/core/src/com/google/inject/spi/Elements.java b/core/src/com/google/inject/spi/Elements.java
index f507fe03..986582e2 100644
--- a/core/src/com/google/inject/spi/Elements.java
+++ b/core/src/com/google/inject/spi/Elements.java
@@ -302,7 +302,11 @@ public final class Elements {
}
public <T> Provider<T> getProvider(final Key<T> key) {
- final ProviderLookup<T> element = new ProviderLookup<T>(getElementSource(), key);
+ return getProvider(Dependency.get(key));
+ }
+
+ public <T> Provider<T> getProvider(final Dependency<T> dependency) {
+ final ProviderLookup<T> element = new ProviderLookup<T>(getElementSource(), dependency);
elements.add(element);
return element.getProvider();
}
diff --git a/core/src/com/google/inject/spi/InjectionPoint.java b/core/src/com/google/inject/spi/InjectionPoint.java
index 653f87c8..267e0abb 100644
--- a/core/src/com/google/inject/spi/InjectionPoint.java
+++ b/core/src/com/google/inject/spi/InjectionPoint.java
@@ -307,6 +307,20 @@ public final class InjectionPoint {
}
/**
+ * Returns a new injection point for the specified method of {@code type}.
+ * This is useful for extensions that need to build dependency graphs from
+ * arbitrary methods.
+ *
+ * @param method any single method present on {@code type}.
+ * @param type the concrete type that defines {@code method}.
+ *
+ * @since 4.0
+ */
+ public static <T> InjectionPoint forMethod(Method method, TypeLiteral<T> type) {
+ return new InjectionPoint(type, method, false);
+ }
+
+ /**
* Returns all static method and field injection points on {@code type}.
*
* @return a possibly empty set of injection points. The set has a specified iteration order. All
diff --git a/core/src/com/google/inject/spi/ProviderLookup.java b/core/src/com/google/inject/spi/ProviderLookup.java
index e4b5da60..a2324315 100644
--- a/core/src/com/google/inject/spi/ProviderLookup.java
+++ b/core/src/com/google/inject/spi/ProviderLookup.java
@@ -39,12 +39,17 @@ import java.util.Set;
*/
public final class ProviderLookup<T> implements Element {
private final Object source;
- private final Key<T> key;
+ private final Dependency<T> dependency;
private Provider<T> delegate;
public ProviderLookup(Object source, Key<T> key) {
+ this(source, Dependency.get(checkNotNull(key, "key")));
+ }
+
+ /** @since 4.0 */
+ public ProviderLookup(Object source, Dependency<T> dependency) {
this.source = checkNotNull(source, "source");
- this.key = checkNotNull(key, "key");
+ this.dependency = checkNotNull(dependency, "dependency");
}
public Object getSource() {
@@ -52,7 +57,11 @@ public final class ProviderLookup<T> implements Element {
}
public Key<T> getKey() {
- return key;
+ return dependency.getKey();
+ }
+
+ public Dependency<T> getDependency() {
+ return dependency;
}
public <T> T acceptVisitor(ElementVisitor<T> visitor) {
@@ -70,7 +79,7 @@ public final class ProviderLookup<T> implements Element {
}
public void applyTo(Binder binder) {
- initializeDelegate(binder.withSource(getSource()).getProvider(key));
+ initializeDelegate(binder.withSource(getSource()).getProvider(dependency));
}
/**
@@ -93,16 +102,16 @@ public final class ProviderLookup<T> implements Element {
"This Provider cannot be used until the Injector has been created.");
return delegate.get();
}
-
+
public Set<Dependency<?>> getDependencies() {
// We depend on Provider<T>, not T directly. This is an important distinction
// for dependency analysis tools that short-circuit on providers.
- Key<?> providerKey = key.ofType(Types.providerOf(key.getTypeLiteral().getType()));
+ Key<?> providerKey = getKey().ofType(Types.providerOf(getKey().getTypeLiteral().getType()));
return ImmutableSet.<Dependency<?>>of(Dependency.get(providerKey));
}
@Override public String toString() {
- return "Provider<" + key.getTypeLiteral() + ">";
+ return "Provider<" + getKey().getTypeLiteral() + ">";
}
};
}
diff --git a/core/test/com/google/inject/spi/ProviderMethodsTest.java b/core/test/com/google/inject/spi/ProviderMethodsTest.java
index cf08e466..58149670 100644
--- a/core/test/com/google/inject/spi/ProviderMethodsTest.java
+++ b/core/test/com/google/inject/spi/ProviderMethodsTest.java
@@ -21,6 +21,8 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
@@ -32,24 +34,33 @@ import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provides;
+import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import com.google.inject.Stage;
+import com.google.inject.TypeLiteral;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.InternalFlags;
import com.google.inject.internal.ProviderMethod;
import com.google.inject.internal.ProviderMethodsModule;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
+import com.google.inject.util.Providers;
import com.google.inject.util.Types;
import junit.framework.TestCase;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
import java.util.logging.Logger;
/**
@@ -211,7 +222,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
public void testGenericProviderMethods() {
Injector injector = Guice.createInjector(
new ProvideTs<String>("A", "B") {}, new ProvideTs<Integer>(1, 2) {});
-
+
assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("First"))));
assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Second"))));
assertEquals(ImmutableSet.of("A", "B"),
@@ -246,7 +257,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
return ImmutableSet.of(first, second);
}
}
-
+
public void testAutomaticProviderMethods() {
Injector injector = Guice.createInjector((Module) new AbstractModule() {
@Override protected void configure() { }
@@ -281,7 +292,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
Injector injector = Guice.createInjector(installsSelf);
assertEquals("A5", injector.getInstance(String.class));
}
-
+
public void testWildcardProviderMethods() {
final List<String> strings = ImmutableList.of("A", "B", "C");
final List<Number> numbers = ImmutableList.<Number>of(1, 2, 3);
@@ -312,8 +323,8 @@ public class ProviderMethodsTest extends TestCase implements Module {
@Inject Class<?> type;
}
- public void testProviderMethodDependenciesAreExposed() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ public void testProviderMethodDependenciesAreExposed() throws Exception {
+ Module module = new AbstractModule() {
@Override protected void configure() {
bind(Integer.class).toInstance(50);
bindConstant().annotatedWith(Names.named("units")).to("Kg");
@@ -321,13 +332,18 @@ public class ProviderMethodsTest extends TestCase implements Module {
@Provides @Named("weight") String provideWeight(Integer count, @Named("units") String units) {
return count + units;
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
ProviderInstanceBinding<?> binding = (ProviderInstanceBinding<?>) injector.getBinding(
Key.get(String.class, Names.named("weight")));
- assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Integer.class)),
- Dependency.get(Key.get(String.class, Names.named("units")))),
- binding.getDependencies());
+ Method method =
+ module.getClass().getDeclaredMethod("provideWeight", Integer.class, String.class);
+ InjectionPoint point = new InjectionPoint(TypeLiteral.get(module.getClass()), method, false);
+ assertEquals(ImmutableSet.<Dependency<?>>of(
+ new Dependency<Integer>(point, Key.get(Integer.class), false, 0),
+ new Dependency<String>(point, Key.get(String.class, Names.named("units")), false, 1)),
+ binding.getDependencies());
}
public void testNonModuleProviderMethods() {
@@ -357,9 +373,9 @@ public class ProviderMethodsTest extends TestCase implements Module {
element instanceof ProviderInstanceBinding);
ProviderInstanceBinding binding = (ProviderInstanceBinding) element;
- javax.inject.Provider provider = binding.getUserSuppliedProvider();
+ javax.inject.Provider provider = binding.getUserSuppliedProvider();
assertTrue(provider instanceof ProviderMethod);
- assertEquals(methodsObject, ((ProviderMethod) provider).getInstance());
+ assertEquals(methodsObject, ((ProviderMethod) provider).getInstance());
assertSame(provider, binding.getProviderInstance());
}
@@ -372,29 +388,29 @@ public class ProviderMethodsTest extends TestCase implements Module {
});
fail();
} catch (CreationException expected) {
- assertContains(expected.getMessage(),
+ assertContains(expected.getMessage(),
"1) Provider methods must return a value. Do not return void.",
getClass().getName(), ".provideFoo(ProviderMethodsTest.java:");
}
}
-
+
public void testInjectsJustOneLogger() {
AtomicReference<Logger> loggerRef = new AtomicReference<Logger>();
Injector injector = Guice.createInjector(new FooModule(loggerRef));
-
+
assertNull(loggerRef.get());
injector.getInstance(Integer.class);
Logger lastLogger = loggerRef.getAndSet(null);
assertNotNull(lastLogger);
injector.getInstance(Integer.class);
assertSame(lastLogger, loggerRef.get());
-
- assertEquals(FooModule.class.getName() + ".foo", lastLogger.getName());
+
+ assertEquals(FooModule.class.getName(), lastLogger.getName());
}
-
+
private static class FooModule extends AbstractModule {
private final AtomicReference<Logger> loggerRef;
-
+
public FooModule(AtomicReference<Logger> loggerRef) {
this.loggerRef = loggerRef;
}
@@ -407,7 +423,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
return 42;
}
}
-
+
public void testSpi() throws Exception {
Module m1 = new AbstractModule() {
@Override protected void configure() {}
@@ -418,7 +434,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
@Provides Integer provideInt(@Named("foo") String dep) { return 42; }
};
Injector injector = Guice.createInjector(m1, m2);
-
+
Binding<String> stringBinding =
injector.getBinding(Key.get(String.class, Names.named("foo")));
ProvidesMethodBinding<String> stringMethod =
@@ -429,7 +445,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
assertEquals(((HasDependencies) stringBinding).getDependencies(),
stringMethod.getDependencies());
assertEquals(Key.get(String.class, Names.named("foo")), stringMethod.getKey());
-
+
Binding<Integer> intBinding = injector.getBinding(Integer.class);
ProvidesMethodBinding<Integer> intMethod =
intBinding.acceptTargetVisitor(new BindingCapturer<Integer>());
@@ -439,21 +455,21 @@ public class ProviderMethodsTest extends TestCase implements Module {
assertEquals(((HasDependencies) intBinding).getDependencies(),
intMethod.getDependencies());
assertEquals(Key.get(Integer.class), intMethod.getKey());
-
+
}
-
+
private static class BindingCapturer<T> extends DefaultBindingTargetVisitor<T, ProvidesMethodBinding<T>>
implements ProvidesMethodTargetVisitor<T, ProvidesMethodBinding<T>> {
-
+
@SuppressWarnings("unchecked")
public ProvidesMethodBinding<T> visit(
ProvidesMethodBinding<? extends T> providesMethodBinding) {
return (ProvidesMethodBinding<T>)providesMethodBinding;
}
-
+
@Override protected ProvidesMethodBinding<T> visitOther(Binding<? extends T> binding) {
throw new IllegalStateException("unexpected visit of: " + binding);
- }
+ }
}
public void testProvidesMethodVisibility() {
@@ -495,7 +511,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
fail("Expected injector creation failure");
} catch (CreationException expected) {
// both of our super class bindings cause errors
- assertContains(expected.getMessage(),
+ assertContains(expected.getMessage(),
"A binding to java.lang.Long was already configured",
"A binding to java.lang.Integer was already configured");
}
@@ -561,14 +577,14 @@ public class ProviderMethodsTest extends TestCase implements Module {
public void testShareFastClassWithSuperClass() {
CallerInspecterSubClassModule module = new CallerInspecterSubClassModule();
Guice.createInjector(Stage.PRODUCTION, module);
- assertEquals("Expected provider methods in the same class to share fastclass classes",
+ assertEquals("Expected provider methods in the same class to share fastclass classes",
module.fooCallerClass, module.barCallerClass);
assertFalse(
"Did not expect provider methods in the subclasses to share fastclass classes "
+ "with their parent classes",
module.bazCallerClass.equals(module.barCallerClass));
}
-
+
private static class CallerInspecterSubClassModule extends CallerInspecterModule {
String bazCallerClass;
@@ -594,7 +610,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
@Provides @Named("unrawlist") List<String> rawParameterProvider(@Named("rawlist") List f) {
return f;
}
-
+
@Provides @Named("list") List<String> annotatedGenericProviderMethod() {
return new ArrayList<String>();
}
@@ -617,7 +633,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -634,7 +650,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -651,7 +667,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -667,13 +683,13 @@ public class ProviderMethodsTest extends TestCase implements Module {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
}
}
-
+
public void testOverrideProviderMethod_covariantOverrideDoesntHaveProvides() {
class SubClassModule extends SuperClassModule {
@@ -685,7 +701,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -702,7 +718,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -747,11 +763,11 @@ public class ProviderMethodsTest extends TestCase implements Module {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
- "@Provides method: " + SuperClassModule.class.getName()
+ "@Provides method: " + SuperClassModule.class.getName()
+ ".annotatedGenericParameterProviderMethod()",
- "overridden by: " + SubClassModule.class.getName()
+ "overridden by: " + SubClassModule.class.getName()
+ ".annotatedGenericParameterProviderMethod()");
}
}
@@ -767,7 +783,7 @@ public class ProviderMethodsTest extends TestCase implements Module {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".rawProvider()",
"overridden by: " + SubClassModule.class.getName() + ".rawProvider()");
@@ -836,4 +852,101 @@ public class ProviderMethodsTest extends TestCase implements Module {
public void testIgnoreSyntheticBridgeMethods() {
Guice.createInjector(new ModuleImpl());
}
+
+ public void testNullability() throws Exception {
+ Module module = new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(String.class).toProvider(Providers.<String>of(null));
+ }
+
+ @SuppressWarnings("unused")
+ @Provides
+ Integer fail(String foo) {
+ return 1;
+ }
+
+ @SuppressWarnings("unused")
+ @Provides
+ Long succeed(@Nullable String foo) {
+ return 2L;
+ }
+ };
+ Injector injector = Guice.createInjector(module);
+ InjectionPoint fooPoint = InjectionPoint.forMethod(
+ module.getClass().getDeclaredMethod("fail", String.class),
+ TypeLiteral.get(module.getClass()));
+ Dependency<?> fooDependency = Iterables.getOnlyElement(fooPoint.getDependencies());
+
+ runNullableTest(injector, fooDependency, module);
+
+ injector.getInstance(Long.class);
+ }
+
+ private void runNullableTest(Injector injector, Dependency<?> dependency, Module module) {
+ switch (InternalFlags.getNullableProvidesOption()) {
+ case ERROR:
+ validateNullableFails(injector, module);
+ break;
+ case IGNORE:
+ validateNullableIgnored(injector);
+ break;
+ case WARN:
+ validateNullableWarns(injector, dependency);
+ break;
+ }
+ }
+
+ private void validateNullableFails(Injector injector, Module module) {
+ try {
+ injector.getInstance(Integer.class);
+ fail();
+ } catch (ProvisionException expected) {
+ assertContains(expected.getMessage(),
+ "1) null returned by binding at " + module.getClass().getName() + ".configure(",
+ "but parameter 0 of " + module.getClass().getName() + ".fail() is not @Nullable",
+ "while locating java.lang.String",
+ "for parameter 0 at " + module.getClass().getName() + ".fail(",
+ "while locating java.lang.Integer");
+
+ assertEquals(1, expected.getErrorMessages().size());
+ }
+ }
+
+ private void validateNullableIgnored(Injector injector) {
+ injector.getInstance(Integer.class); // no exception
+ }
+
+ private void validateNullableWarns(Injector injector, Dependency<?> dependency) {
+ final List<LogRecord> logRecords = Lists.newArrayList();
+ final Handler fakeHandler = new Handler() {
+ @Override
+ public void publish(LogRecord logRecord) {
+ logRecords.add(logRecord);
+ }
+ @Override
+ public void flush() {}
+ @Override
+ public void close() throws SecurityException {}
+ };
+ Logger.getLogger(Guice.class.getName()).addHandler(fakeHandler);
+ try {
+ injector.getInstance(Integer.class); // no exception, but assert it does log.
+ LogRecord record = Iterables.getOnlyElement(logRecords);
+ assertEquals(
+ "Guice injected null into parameter {0} of {1} (a {2}), please mark it @Nullable."
+ + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an"
+ + " error.",
+ record.getMessage());
+ assertEquals(dependency.getParameterIndex(), record.getParameters()[0]);
+ assertEquals(Errors.convert(dependency.getInjectionPoint().getMember()),
+ record.getParameters()[1]);
+ assertEquals(Errors.convert(dependency.getKey()), record.getParameters()[2]);
+ } finally {
+ Logger.getLogger(Guice.class.getName()).removeHandler(fakeHandler);
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface Nullable {}
}