diff options
author | sameb <sameb@google.com> | 2015-02-02 12:45:25 -0800 |
---|---|---|
committer | Sam Berlin <sameb@google.com> | 2015-02-03 18:07:07 -0500 |
commit | 9867f9c2142355ae958f9eeb8fb96811082c8812 (patch) | |
tree | fb4ee51e5e4bfead67e2951c74958cdfe8b29194 | |
parent | f11bf2d26ccd07e72805b789b33d855e223b1ed7 (diff) | |
download | guice-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
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 {} } |